2、OpenCV核心基础(一):图像的读取、显示与保存、像素操作与色彩空间转换(BGR/RGB/HSV)

好,咱们正式开始动手写代码了。这一章是OpenCV的“地基”,说白了就是怎么把一张图片从硬盘里请出来,在屏幕上亮个相,再存回去。另外,还得搞清楚像素到底是个啥,以及为什么OpenCV读出来的颜色跟你想象的不太一样。

我个人习惯,每学一个新库,第一件事就是跑通它的“Hello World”——也就是图像读写。这一步通了,后面所有视觉任务才有了操作对象。

2.1 图像的读取、显示与保存

OpenCV里最常用的三个函数:imread、imshow、imwrite。名字很直白,读、显、写。

2.1.1 读取图像:cv2.imread()

函数签名很简单:

img = cv2.imread(filename, flags)

filename是图片路径,支持路径和相对路径。flags是读取模式,常用这几个:

flags 值 含义 场景
cv2.IMREAD_COLOR (默认) 加载彩色图,忽略透明度 日常彩色图像处理
cv2.IMREAD_GRAYSCALE 加载为单通道灰度图 边缘检测、二值化前
cv2.IMREAD_UNCHANGED 加载所有通道(含Alpha) 处理PNG透明图
我曾经踩过的坑:路径里有中文!OpenCV的imread对中文路径支持不好,返回None还不报错。我早期一个项目,客户传的图片名全是中文,调试了半天才发现是路径问题。建议项目里统一用英文路径,或者用cv2.imdecode配合np.fromfile处理中文路径。

2.1.2 显示图像:cv2.imshow()

显示图像需要配合两个函数:

cv2.imshow('窗口标题', img)
cv2.waitKey(0)      # 等待按键
cv2.destroyAllWindows()  # 关闭所有窗口

waitKey(0)表示无限等待,直到你按任意键。如果你想让窗口自动关闭,可以设置毫秒数,比如waitKey(1000)就是显示1秒后自动关闭。

小技巧:我习惯把waitKey(0) & 0xFF == ord('q')作为退出条件,这样按'q'键退出,按其他键继续。这在视频处理里特别实用。

2.1.3 保存图像:cv2.imwrite()

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

保存格式由文件扩展名决定。JPEG可以指定质量参数:

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

质量范围0-100,默认95。数值越大,文件越大,画质越好。

2.2 像素操作:图像的本质是矩阵

你想想看,一张图像在计算机眼里是什么?就是一个多维数组。灰度图是二维数组,彩色图是三维数组(高×宽×通道数)。

OpenCV读取彩色图时,通道顺序是BGR,不是我们熟悉的RGB。这一点特别容易搞混。

2.2.1 访问和修改像素值

import cv2
import numpy as np

img = cv2.imread('lena.jpg')

# 访问(100, 200)位置的像素
px = img[100, 200]
print(px)  # [B, G, R] 三个值

# 修改像素
img[100, 200] = [255, 0, 0]  # 变成蓝色

# 访问单通道
blue = img[100, 200, 0]  # B通道
green = img[100, 200, 1] # G通道
red = img[100, 200, 2]   # R通道
核心理解:图像坐标系中,img[row, col],row是y方向(高度),col是x方向(宽度)。千万别搞反了,我见过不少新手把行列顺序写反,结果图像显示出来是扭曲的。

2.2.2 图像属性

print(img.shape)   # (高度, 宽度, 通道数)
print(img.size)    # 总像素数 = 高×宽×通道数
print(img.dtype)   # 数据类型,通常是 uint8

shape返回的是一个元组。灰度图只有两个值,彩色图有三个。

2.2.3 ROI(感兴趣区域)操作

用切片就能提取图像的一块区域:

# 提取从(50,100)到(200,300)的区域
roi = img[50:200, 100:300]

# 把这块区域复制到另一个位置
img[10:160, 10:210] = roi

这个操作在目标检测里太常用了。比如YOLO检测到目标后,你要把目标区域截出来做进一步分析,用的就是ROI切片。

2.3 色彩空间转换:BGR、RGB、HSV

为什么OpenCV用BGR而不是RGB?这其实是个历史遗留问题。早期相机硬件厂商用BGR顺序,OpenCV为了兼容就这么定了。你只要记住:OpenCV默认是BGR,显示的时候没问题,但如果你用matplotlib显示,颜色就全乱了。

2.3.1 BGR ↔ RGB 转换

# 方法一:用cvtColor
rgb_img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)

# 方法二:手动翻转通道
rgb_img = img[:, :, ::-1]  # 用切片反转通道顺序

我个人更推荐用cvtColor,语义清晰,不容易出错。

2.3.2 BGR ↔ HSV 转换

HSV色彩空间把颜色分解成色调(H)、饱和度(S)、明度(V)。为什么需要HSV?因为它在做颜色分割时比BGR/RGB好用得多。

hsv_img = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)

HSV各通道范围:

通道 OpenCV范围 说明
H (色调) 0 - 179 标准HSV是0-360,OpenCV压缩了一半
S (饱和度) 0 - 255 0为灰色,255最鲜艳
V (明度) 0 - 255 0为黑色,255最亮
注意:OpenCV的H范围是0-179,不是0-360。如果你在网上查HSV颜色范围,记得把H值除以2。我曾经直接复制网上的颜色范围,结果死活分割不出红色物体,后来才发现是H范围没换算。

2.3.3 实战:用HSV做颜色检测

假设我们要检测图像中的蓝色物体:

import cv2
import numpy as np

img = cv2.imread('objects.jpg')
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)

# 蓝色的HSV范围(OpenCV范围)
lower_blue = np.array([100, 50, 50])
upper_blue = np.array([130, 255, 255])

# 生成掩码
mask = cv2.inRange(hsv, lower_blue, upper_blue)

# 提取蓝色区域
result = cv2.bitwise_and(img, img, mask=mask)

cv2.imshow('Original', img)
cv2.imshow('Mask', mask)
cv2.imshow('Result', result)
cv2.waitKey(0)

这段代码就是颜色检测的经典套路。YOLO检测到目标后,如果你还想根据颜色做二次筛选,HSV就是最好的工具。

2.4 本章知识体系

下面这张图把本章的核心知识点串起来了:

OpenCV核心基础知识体系 OpenCV核心基础:图像读写、像素操作与色彩空间 图像读写 像素操作 色彩空间转换 imread() imshow() imwrite() 像素访问 图像属性 ROI切片 BGR↔RGB BGR↔HSV 颜色检测 应用:图像预处理 → 颜色分割 → ROI提取 → 目标检测输入 ⚠ 关键提醒:OpenCV默认BGR顺序,HSV的H范围0-179 ⚠ 避坑:中文路径、行列顺序、颜色范围换算 本章知识体系:三大模块 + 关键提醒 + 应用链路

2.5 本章小结

这一章的内容,说白了就是OpenCV的“三板斧”:

  • 图像读写:imread、imshow、imwrite,记住路径别带中文
  • 像素操作:图像就是矩阵,用[row, col]访问,用切片做ROI
  • 色彩空间:BGR是OpenCV的默认顺序,HSV做颜色分割特别好用

这些基础操作,后面每一章都会用到。尤其是HSV颜色检测,在YOLO检测后的二次过滤里非常实用。你可以在自己的项目里试试:拍一张有彩色物体的照片,用HSV提取特定颜色,看看效果怎么样。