3、目标检测基础:颜色空间转换(RGB/HSV)、阈值分割、轮廓提取与筛选
做视觉追踪,第一步就是让机器「看见」目标。但摄像头拍下来的原始图像,说白了就是一串数字——RGB的数值。你想想看,如果让你在RGB空间里找一块红色积木,是不是很头疼?因为光照一变,R、G、B三个值全在跳。我刚开始做项目时就被这个坑过,后来才明白,得换个颜色空间。
3.1 颜色空间转换:从RGB到HSV
RGB是给显示器看的,不是给算法用的。HSV(色调Hue、饱和度Saturation、明度Value)才是更适合做颜色分割的空间。为什么呢?因为HSV把「颜色是什么」和「颜色有多亮」分开了。
举个例子:一个红色苹果,在强光下RGB可能是(200,30,20),在弱光下变成(100,15,10)。但在HSV空间里,H值基本不变,都是0度附近。这就是我偏爱HSV的原因——对光照变化更鲁棒。
转换公式其实不复杂,但每次手写容易出错。我个人习惯直接用OpenCV的函数:
import cv2
import numpy as np
# 读取图像
img = cv2.imread('target.jpg')
# BGR转HSV(注意OpenCV默认是BGR顺序)
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
嗯,这里要注意:OpenCV里默认通道顺序是BGR,不是RGB。我第一次用的时候就搞反了,结果分割出来的颜色完全不对。后来我养成了一个习惯——每次转换前先用cv2.split()看一眼通道顺序。
3.2 阈值分割:把目标从背景里抠出来
有了HSV图像,下一步就是设定阈值,把目标区域变成白色,背景变成黑色。说白了就是二值化。
比如我要追踪一个蓝色小球,我会先标定它的H值范围。我建议的做法是:
- 拍一张包含目标的图像
- 用鼠标取几个点的HSV值
- 观察H值的波动范围,适当放宽10-20个单位
代码实现很简单:
# 设定蓝色的HSV范围
lower_blue = np.array([100, 50, 50])
upper_blue = np.array([130, 255, 255])
# 生成二值掩码
mask = cv2.inRange(hsv, lower_blue, upper_blue)
这里有个坑:H值在OpenCV里是0-180,不是0-360。我曾经在项目里直接用0-360的范围,结果分割出来的区域全是黑的。排查了半天才发现是范围问题。
阈值分割后,图像上会有很多噪点。我一般会用形态学操作处理一下:
# 先腐蚀再膨胀,去除小噪点
kernel = np.ones((5,5), np.uint8)
mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel)
3.3 轮廓提取:找到目标的边界
二值图像有了,接下来要提取轮廓。OpenCV的findContours函数是主力工具。我记得第一次用的时候,被它的返回值搞晕了——不同版本的OpenCV返回值还不一样。
现在统一用这种方式:
# 提取轮廓
contours, hierarchy = cv2.findContours(
mask,
cv2.RETR_EXTERNAL, # 只提取最外层轮廓
cv2.CHAIN_APPROX_SIMPLE # 压缩轮廓点
)
参数选择有讲究:
- RETR_EXTERNAL:只取最外层轮廓。如果你只关心目标的外边界,这个最合适。
- RETR_TREE:提取所有层级轮廓。当目标内部有空洞时用这个。
- CHAIN_APPROX_SIMPLE:压缩水平、垂直、对角线方向的点,只保留端点。节省内存。
3.4 轮廓筛选:去伪存真
提取出来的轮廓可能很多——有目标、有噪点、有背景干扰。怎么找到真正的目标?我一般用三个指标筛选:
| 筛选指标 | 计算方法 | 典型阈值 |
|---|---|---|
| 面积 | cv2.contourArea(contour) |
大于100像素 |
| 周长 | cv2.arcLength(contour, True) |
大于50像素 |
| 宽高比 | 外接矩形的宽/高 | 0.5 ~ 2.0 |
实际代码中我会这样写:
valid_contours = []
for cnt in contours:
area = cv2.contourArea(cnt)
if area < 100:
continue # 面积太小,跳过
# 计算外接矩形
x, y, w, h = cv2.boundingRect(cnt)
aspect_ratio = w / h
if aspect_ratio < 0.5 or aspect_ratio > 2.0:
continue # 形状太奇怪,跳过
valid_contours.append(cnt)
我曾经在一个项目中追踪圆形零件,结果总是把螺丝刀头也识别进来。后来加了「圆形度」指标——4 * pi * area / (perimeter^2),接近1的才是圆。这才把问题解决。
3.5 本章知识体系
下面这张图总结了颜色分割的完整流程。你可以把它当作一个检查清单——每次做目标检测时,按这个步骤走一遍,基本不会漏掉关键环节。
这套流程我用了很多年,从简单的颜色追踪到复杂的多目标检测,核心逻辑都没变过。你想想看,不管目标多复杂,第一步永远是「把目标从背景里分离出来」。颜色分割就是最直接、最快速的方法。
当然,它也有局限——如果目标和背景颜色相近,或者光照剧烈变化,HSV阈值就不够用了。那时候我会考虑用机器学习的方法。不过那是后面章节的内容了,先把基础打牢再说。
核心要点回顾:
- HSV比RGB更适合做颜色分割,因为H值对光照不敏感
- 阈值分割后一定要做形态学处理,否则噪点会干扰轮廓提取
- 轮廓筛选至少用面积和宽高比两个指标,单一指标容易误判
- 调试时先打印数据再设阈值,别凭感觉写死数值
好,这一章就到这里。记住这个流程,下一章我们会在这个基础上加入运动预测,让追踪更平滑、更稳定。
公众号:蓝海资料掘金营,微信deep3321