2、OpenCV基础与图像操作:图像读取与显示、图像属性获取、图像保存、图像缩放与裁剪、颜色空间转换、图像绘制(矩形、圆形、文字)

好,咱们正式开始动手了。这一章是地基中的地基——图像的基本操作。你想想看,不管后面做多复杂的目标检测,第一步永远是把图像读进来、看一眼、确认没问题,然后才谈得上处理。我见过不少新手,模型调得飞起,结果连图像路径写错了都不知道,白白浪费半天时间。

所以这一章,咱们把 OpenCV 最常用的几个图像操作彻底搞明白。别嫌简单,这些操作我到现在每天都在用。

2.1 图像读取与显示

OpenCV 读取图像用 cv2.imread(),显示用 cv2.imshow()。就这么两个函数,但细节不少。

核心函数速查

  • cv2.imread(path, flag) — 读取图像,返回 NumPy 数组
  • cv2.imshow(winname, img) — 显示图像
  • cv2.waitKey(delay) — 等待键盘输入,单位毫秒
  • cv2.destroyAllWindows() — 关闭所有窗口

读取时的 flag 参数很关键。我个人习惯用这三个:

Flag 值 含义 适用场景
cv2.IMREAD_COLOR (1) 彩色图像,忽略透明度 绝大多数情况
cv2.IMREAD_GRAYSCALE (0) 灰度图像 边缘检测、模板匹配
cv2.IMREAD_UNCHANGED (-1) 包含 Alpha 通道 PNG 透明图处理

来看一段最基础的代码:

import cv2

# 读取图像
img = cv2.imread('cat.jpg')

# 检查是否读取成功
if img is None:
    print("图像读取失败,检查路径!")
    exit()

# 显示图像
cv2.imshow('My Image', img)

# 等待按键,0 表示无限等待
cv2.waitKey(0)

# 清理窗口
cv2.destroyAllWindows()

我曾经踩过的坑:图像路径不要带中文!OpenCV 底层用的是 C 函数,中文路径直接返回 None,而且不报错。你查半天还以为是代码问题,其实就是路径里有「测试.jpg」这种名字。另外,cv2.imread() 不会抛异常,读取失败只返回 None,所以一定要加 if img is None 检查。

2.2 图像属性获取

图像在 OpenCV 里就是一个 NumPy 数组。它的属性就是数组的属性。我经常用这三个:

  • shape — 返回 (高度, 宽度, 通道数)。注意!OpenCV 里 shape 的第一个值是行数(高度),第二个是列数(宽度),跟很多人直觉相反。
  • dtype — 数据类型,通常是 uint8,范围 0-255。
  • size — 像素总数,等于 高 × 宽 × 通道数。
print(f"图像形状: {img.shape}")      # 例如 (480, 640, 3)
print(f"数据类型: {img.dtype}")       # uint8
print(f"像素总数: {img.size}")        # 480*640*3 = 921600

这里有个容易混淆的点:img.shape[0] 是高度,img.shape[1] 是宽度。我刚开始学的时候老搞反,后来记住「先高后宽,先行后列」就再也没错过。

2.3 图像保存

保存用 cv2.imwrite()。很简单,但有个细节:

cv2.imwrite('output.jpg', img)

它会根据文件扩展名自动选择编码格式。保存 JPG 时可以控制质量:

cv2.imwrite('output.jpg', img, [cv2.IMWRITE_JPEG_QUALITY, 95])

质量参数范围 0-100,默认 95。数值越大文件越大,画质越好。我一般用 90-95,平衡体积和画质。

小技巧:保存 PNG 时可以设置压缩级别,cv2.IMWRITE_PNG_COMPRESSION,范围 0-9,默认 3。数值越大压缩越狠,但速度越慢。我一般用 6,体积和速度比较均衡。

2.4 图像缩放与裁剪

这两个操作在目标检测预处理里几乎必用。YOLO 输入尺寸通常是 640×640,你拍的照片可能是 1920×1080,不缩放怎么行?

缩放:cv2.resize()

# 指定目标尺寸
resized = cv2.resize(img, (640, 480))

# 按比例缩放
scale_percent = 50  # 缩小到 50%
width = int(img.shape[1] * scale_percent / 100)
height = int(img.shape[0] * scale_percent / 100)
resized = cv2.resize(img, (width, height))

插值方法的选择:

插值方法 适用场景
cv2.INTER_NEAREST 速度最快,效果最差,有锯齿
cv2.INTER_LINEAR 默认方法,速度和质量均衡
cv2.INTER_CUBIC 放大时效果好,但速度慢
cv2.INTER_AREA 缩小时效果好,避免摩尔纹

我个人习惯:缩小用 INTER_AREA,放大用 INTER_LINEAR 或 INTER_CUBIC。

裁剪:NumPy 切片

裁剪不需要调用函数,直接用数组切片就行:

# 裁剪 [y1:y2, x1:x2]
cropped = img[100:400, 200:500]

注意顺序:先 y 后 x,先高后宽。跟 shape 的顺序一致。

注意:裁剪时坐标不要越界,否则会报错或者得到空数组。我一般会加个边界检查:y2 = min(y2, img.shape[0])。

2.5 颜色空间转换

OpenCV 默认读取的图像是 BGR 格式,不是 RGB。这一点太容易踩坑了。你用 matplotlib 显示图像,颜色会偏蓝偏怪,就是因为 OpenCV 的 BGR 和 matplotlib 的 RGB 顺序反了。

# BGR 转灰度
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# BGR 转 RGB
rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)

# BGR 转 HSV
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)

HSV 在颜色检测里特别有用。比如你要检测红色物体,在 BGR 空间里很难设定阈值,但在 HSV 空间里,只需要锁定 H 通道的范围就行。

经验之谈:OpenCV 里 HSV 的 H 范围是 0-180,不是 0-360。S 和 V 范围是 0-255。这个跟其他库不一样,注意区分。

2.6 图像绘制

画框、画圆、写字——目标检测结果可视化全靠这些。YOLO 检测到目标后,你要在图像上画出边界框、标出类别和置信度,这些操作就是基础。

画矩形

cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2)

参数依次是:图像、左上角坐标、右下角坐标、颜色 (BGR)、线宽。线宽为 -1 时填充整个矩形。

画圆形

cv2.circle(img, (cx, cy), radius, (0, 0, 255), -1)

参数:图像、圆心、半径、颜色、线宽。线宽 -1 表示实心圆。

写文字

cv2.putText(img, 'Cat', (x1, y1-10), 
            cv2.FONT_HERSHEY_SIMPLEX, 0.9, (255, 255, 255), 2)

参数:图像、文字内容、左下角坐标、字体、字号、颜色、线宽。

来看一个完整的绘制示例:

import cv2
import numpy as np

# 创建空白图像
img = np.zeros((400, 600, 3), dtype=np.uint8)
img[:] = (240, 240, 240)  # 浅灰色背景

# 画矩形
cv2.rectangle(img, (50, 50), (250, 200), (0, 255, 0), 3)

# 画圆形
cv2.circle(img, (400, 125), 75, (0, 0, 255), -1)

# 写文字
cv2.putText(img, 'OpenCV Demo', (50, 350),
            cv2.FONT_HERSHEY_SIMPLEX, 1.2, (0, 0, 0), 2)

cv2.imshow('Drawing', img)
cv2.waitKey(0)
cv2.destroyAllWindows()

本章知识结构

下面这张图把本章的核心操作串起来了。从读取图像开始,到各种处理,再到保存结果,流程很清晰。

OpenCV 图像操作核心流程 cv2.imread() 读取图像 获取属性 shape/dtype/size 图像处理操作 缩放与裁剪 颜色空间转换 图像绘制 cv2.imshow() 显示结果 cv2.imwrite() 保存图像

嗯,这一章的内容就这些。看起来操作很多,其实核心就是几个函数来回用。你把这些练熟了,后面做目标检测的预处理和后处理就会非常顺手。记住:图像是 NumPy 数组,操作数组就是操作图像。这个观念贯穿整个 OpenCV。