#OpenCV快速入门:图像读取、绘制与几何变换完整指南
#引言
OpenCV(Open Source Computer Vision Library)是计算机视觉领域事实标准级的开源工具库,不仅提供了从低阶像素操作到高阶视觉算法的全链路支持,还能跨平台、跨语言(C++/Python/Java等)调用,速度极快(底层为优化后的C/C++)。
本文聚焦传统CV的第一块基石:OpenCV基础IO、绘图、几何变换,所有代码都附有清晰注释和使用场景,看完就能直接上手写实用脚本。
📂 所属阶段:第一阶段 — 图像处理基石(传统 CV 篇)
🔗 相关章节:CV 概览与数字图像基础 · 图像增强与滤波
#1. 环境搭建:5分钟上手OpenCV
#1.1 库的选择与安装
OpenCV有3个主流Python包,根据你的需求选一个即可:
# ✅ 日常开发首选(包含常用功能)
pip install opencv-python
# ✅ 进阶/科研推荐(额外集成SIFT/SURF等非自由/实验算法)
pip install opencv-contrib-python
# ✅ 服务器环境专用(无GUI依赖,节省资源)
pip install opencv-python-headless#1.2 一键验证安装
复制这段代码运行,没问题就说明环境搭好了:
import cv2
import numpy as np
import matplotlib.pyplot as plt
def check_opencv_env():
"""检查OpenCV核心功能是否正常"""
# 1. 打印版本
print(f"✅ OpenCV版本: {cv2.__version__}")
# 2. 检查NumPy兼容性(OpenCV图像本质是NumPy数组)
print(f"✅ NumPy版本: {np.__version__}")
# 3. 尝试创建空白画布(基础绘图+内存分配验证)
test_canvas = np.zeros((200, 200, 3), dtype=np.uint8)
cv2.putText(test_canvas, "OpenCV Ready!", (20, 110),
cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2)
print("✅ 画布创建成功")
# 4. 检查Matplotlib字体(用于Jupyter显示中文)
plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei', 'Arial Unicode MS']
plt.rcParams['axes.unicode_minus'] = False
print("✅ Matplotlib中文配置完成")
check_opencv_env()#2. 图像IO:读取→显示→保存的全套逻辑
#💡 核心前置知识点
OpenCV存储图像的坑点必须先记牢:
- 彩色图像默认是 BGR格式(和Matplotlib、PIL的RGB相反)
- 图像数据本质是
NumPy uint8数组,取值范围0-255 - 形状顺序是
(高度, 宽度, 通道数),不是直觉上的宽×高
#2.1 图像读取:处理加载失败的场景
很多新手会卡在这里——因为文件路径写错但没加判断!
import cv2
# 三种常用读取模式
MODES = {
"COLOR": cv2.IMREAD_COLOR, # 彩色(默认,忽略alpha)
"GRAY": cv2.IMREAD_GRAYSCALE, # 灰度
"UNCHANGED": cv2.IMREAD_UNCHANGED # 含alpha通道(PNG/GIF支持)
}
def load_image(image_path: str, mode: str = "COLOR"):
"""安全的图像加载函数"""
img = cv2.imread(image_path, MODES.get(mode.upper(), MODES["COLOR"]))
if img is None:
raise FileNotFoundError(f"❌ 图像路径不存在或格式不支持: {image_path}")
print(f"✅ 图像加载成功 | 形状: {img.shape} | 类型: {img.dtype}")
return img
# 使用示例
# img = load_image("test.jpg", "GRAY")#2.2 图像显示:两种场景选对工具
#场景1:本地脚本/调试(用OpenCV原生窗口)
原生窗口支持键盘、鼠标交互,适合快速测试:
def show_opencv_window(img, window_name: str = "OpenCV Window", wait_ms: int = 0):
"""
显示OpenCV原生窗口
wait_ms: 等待毫秒数,0=无限等待,直到按任意键
"""
cv2.imshow(window_name, img)
key = cv2.waitKey(wait_ms) & 0xFF # 兼容64位系统
cv2.destroyAllWindows() # 退出后清理窗口,避免内存泄漏
return key # 返回按键ASCII码,可用于交互
# 使用示例:按ESC键退出,按S键保存
# key = show_opencv_window(img)
# if key == 27: # ESC
# print("退出")
# elif key == ord('s'):
# cv2.imwrite("saved.jpg", img)#场景2:Jupyter Notebook/Lab(用Matplotlib)
原生窗口在Notebook里体验极差,用Matplotlib要注意BGR转RGB:
import matplotlib.pyplot as plt
def show_plt(img, title: str = "图像", figsize: tuple = (8, 6), cmap: str = None):
"""
Matplotlib显示OpenCV图像(自动处理BGR→RGB)
cmap: 灰度图用'gray',彩色图留空
"""
plt.figure(figsize=figsize)
# 处理通道
if len(img.shape) == 3:
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
else:
cmap = 'gray' if cmap is None else cmap
# 显示
plt.imshow(img, cmap=cmap)
plt.title(title, fontsize=14)
plt.axis('off') # 隐藏坐标轴
plt.tight_layout() # 自动调整布局
plt.show()
# 使用示例
# show_plt(img, "测试图像", figsize=(10, 7))#2.3 图像保存:控制压缩质量
def save_image(img, output_path: str, jpg_quality: int = 95, png_compression: int = 3):
"""
智能保存图像,自动根据后缀调整参数
jpg_quality: JPEG质量,0-100,越高越清晰
png_compression: PNG压缩级别,0-9,越高文件越小但保存越慢
"""
ext = output_path.split('.')[-1].lower()
if ext in ['jpg', 'jpeg']:
params = [cv2.IMWRITE_JPEG_QUALITY, jpg_quality]
elif ext == 'png':
params = [cv2.IMWRITE_PNG_COMPRESSION, png_compression]
else:
params = []
success = cv2.imwrite(output_path, img, params)
if success:
print(f"✅ 图像保存成功: {output_path}")
else:
raise IOError(f"❌ 图像保存失败,请检查路径权限或格式: {output_path}")
return success#3. 基础绘图:给图像加标注、画ROI(感兴趣区域)
OpenCV的绘图函数参数高度统一,记通用模板就行:
cv2.func(图像, 位置参数, 颜色(BGR), 线宽, 线型, 偏移)🔍 注意:
- 线宽
-1表示填充- 颜色顺序是
(蓝, 绿, 红)- 所有坐标都是
(x, y)=(宽度方向, 高度方向)
#3.1 常用图形绘制代码
import cv2
import numpy as np
# 创建400x600的白色画布(宽600,高400)
canvas = 255 * np.ones((400, 600, 3), dtype=np.uint8)
# ---------------------- 1. 直线 ----------------------
cv2.line(canvas, (50, 50), (550, 50), (0, 200, 0), 2) # 绿色实线
cv2.line(canvas, (50, 100), (550, 300), (200, 0, 0), 3, cv2.LINE_AA) # 蓝色抗锯齿粗线
# ---------------------- 2. 矩形 ----------------------
cv2.rectangle(canvas, (80, 120), (220, 220), (0, 0, 200), 2) # 空心红框(ROI常用)
cv2.rectangle(canvas, (280, 120), (420, 220), (0, 200, 200), -1) # 填充黄框
# ---------------------- 3. 圆形 ----------------------
cv2.circle(canvas, (500, 170), 50, (200, 0, 200), 3) # 空心紫圆
cv2.circle(canvas, (150, 320), 30, (200, 200, 0), -1) # 填充青圆
# ---------------------- 4. 椭圆 ----------------------
# 参数:中心, (长轴半径, 短轴半径), 旋转角度, 起始角度, 结束角度, ...
cv2.ellipse(canvas, (350, 320), (60, 30), 30, 0, 360, (100, 100, 100), 2) # 倾斜空心灰椭圆
cv2.ellipse(canvas, (350, 320), (60, 30), 30, 0, 180, (100, 0, 100), -1) # 倾斜填充半椭圆
# ---------------------- 5. 多边形 ----------------------
# 注意:多边形的点必须是numpy.int32的二维数组,形状为(n, 1, 2)
pts = np.array([[450, 260], [500, 230], [550, 260], [530, 310], [470, 310]], np.int32)
pts = pts.reshape((-1, 1, 2)) # 转换为OpenCV要求的格式
cv2.polylines(canvas, [pts], True, (255, 100, 0), 2) # 封闭五边形
cv2.fillPoly(canvas, [pts], (255, 100, 0, 100)) # 半透明填充(如果画布有alpha通道)
# ---------------------- 6. 文字 ----------------------
# 支持的字体有限,推荐用cv2.FONT_HERSHEY_SIMPLEX
cv2.putText(canvas, "OpenCV Drawing Demo", (50, 370),
cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 0), 2, cv2.LINE_AA)
# 显示结果
show_plt(canvas, "绘图综合示例")#4. 几何变换:图像缩放、旋转、平移、仿射
几何变换的核心是变换矩阵,OpenCV提供了两个通用函数:
cv2.warpAffine():处理2×3变换矩阵(仿射变换,缩放/旋转/平移/剪切)cv2.warpPerspective():处理3×3变换矩阵(透视变换,后续章节讲)
#4.1 图像缩放:保持宽高比的实用函数
def resize(img, target_w=None, target_h=None, interp=cv2.INTER_AREA):
"""
智能缩放:支持固定尺寸、固定比例、保持宽高比
interp: 插值方法
- 缩小推荐:cv2.INTER_AREA(区域插值,避免锯齿)
- 放大推荐:cv2.INTER_CUBIC(三次样条,4x4邻域,慢但清晰)
- 快速放大:cv2.INTER_LINEAR(双线性,2x2邻域)
"""
h, w = img.shape[:2]
# 自动计算缩放比例
if target_w and target_h:
# 按比例缩放不超出目标尺寸
scale = min(target_w / w, target_h / h)
new_w, new_h = int(w * scale), int(h * scale)
elif target_w:
new_w = target_w
new_h = int(h * (target_w / w))
elif target_h:
new_h = target_h
new_w = int(w * (target_h / h))
else:
raise ValueError("❌ 必须指定target_w或target_h或两者")
return cv2.resize(img, (new_w, new_h), interpolation=interp)#4.2 图像旋转:不裁剪边缘的高级版
普通的旋转会把边缘切掉,这个函数会自动计算新画布尺寸保留全图:
def rotate(img, angle, center=None, scale=1.0):
"""
旋转图像,支持不裁剪边缘
angle: 旋转角度(正=逆时针,负=顺时针)
"""
h, w = img.shape[:2]
# 默认中心旋转
if center is None:
center = (w // 2, h // 2)
# 获取旋转矩阵
M = cv2.getRotationMatrix2D(center, angle, scale)
# ---------------------- 不裁剪边缘的核心逻辑 ----------------------
# 计算旋转后的画布尺寸
cos = np.abs(M[0, 0])
sin = np.abs(M[0, 1])
new_w = int((h * sin) + (w * cos))
new_h = int((h * cos) + (w * sin))
# 调整旋转矩阵的平移部分,把图像移到新画布中心
M[0, 2] += (new_w // 2) - center[0]
M[1, 2] += (new_h // 2) - center[1]
# --------------------------------------------------------------------
# 应用旋转(背景默认黑色,可改M[2,2]相关,但warpAffine只接受2×3矩阵,改背景用borderValue)
return cv2.warpAffine(img, M, (new_w, new_h), borderValue=(255, 255, 255)) # 白色背景#4.3 仿射变换:三点确定一个平行变换
仿射变换会保持直线的平行性(比如正方形变平行四边形),需要给定原图像的3个点和目标图像的对应3个点:
def affine_transform(img, src_pts, dst_pts, borderValue=(255, 255, 255)):
"""
应用仿射变换
src_pts/dst_pts: numpy数组,形状为(3, 2),(x, y)格式
"""
h, w = img.shape[:2]
# 获取2×3仿射变换矩阵
M = cv2.getAffineTransform(src_pts.astype(np.float32), dst_pts.astype(np.float32))
# 应用变换
return cv2.warpAffine(img, M, (w, h), borderValue=borderValue)
# 使用示例:把图像左上角3个点变换成倾斜的
# src_pts = np.array([[0, 0], [w-1, 0], [0, h-1]], np.float32) # 原图像左上角3个角点
# dst_pts = np.array([[50, 50], [w-100, 0], [0, h-50]], np.float32) # 目标点
# transformed = affine_transform(img, src_pts, dst_pts)#5. 实战小工具:10行代码写一个图片批量缩放器
结合前面的load_image和resize函数,写一个批量处理文件夹内图片的工具:
import os
from tqdm import tqdm # 进度条库,需要先pip install tqdm
def batch_resize(input_dir: str, output_dir: str, target_w=None, target_h=None, interp=cv2.INTER_AREA):
"""
批量缩放文件夹内的所有图片
"""
# 创建输出文件夹
os.makedirs(output_dir, exist_ok=True)
# 获取所有图片文件
img_exts = ['.jpg', '.jpeg', '.png', '.bmp', '.tiff']
img_files = [f for f in os.listdir(input_dir) if os.path.splitext(f)[-1].lower() in img_exts]
# 批量处理
for img_file in tqdm(img_files, desc="批量缩放中"):
try:
# 加载
img = load_image(os.path.join(input_dir, img_file))
# 缩放
resized = resize(img, target_w=target_w, target_h=target_h, interp=interp)
# 保存
save_image(resized, os.path.join(output_dir, img_file))
except Exception as e:
print(f"⚠️ 处理失败 {img_file}: {e}")
# 使用示例:把input文件夹内的所有图片缩放到宽800px,保持宽高比
# batch_resize("input", "output_800w", target_w=800)
