色块识别的完整工作原理(以 OpenCV 为例)
·
色块识别是机器视觉中最简单也最常用的算法,核心逻辑是通过颜色阈值把目标色块从背景中分离出来,再分析分离后的像素区域轮廓特征。整个过程就像 "先按颜色挑出所有目标像素,再把连在一起的像素拼成一个个色块轮廓,最后选出符合要求的那个"。
一、核心前提:为什么不用 BGR 而用 HSV 颜色空间
所有 OpenCV 色块识别 100% 会用到 HSV 颜色空间,而不是 OpenCV 默认输出的 BGR 空间,这是最关键的一点:
- BGR:蓝、绿、红三个通道都受亮度影响极大。同一个红色,在强光下和暗光下 BGR 值会相差几倍,很难用固定阈值筛选。
- HSV:把颜色拆分成三个完全独立的分量(OpenCV 特有数值范围)
- H(色相):0-179°(不是 0-360°),代表颜色本身(红 = 0°/180°,绿 = 60°,蓝 = 120°),几乎不受亮度影响;
- S(饱和度):0-255,代表颜色的鲜艳程度(0 = 纯灰色,255 = 最纯的颜色);
- V(明度):0-255,代表亮度(0 = 纯黑,255 = 纯白);
这样一来,只要固定 H 的范围,就能准确筛选出特定颜色,再通过 S 和 V 过滤掉过暗、过亮和发灰的干扰。
二、完整的 5 步识别流程(OpenCV 原生实现)
2.1 图像采集与预处理
- 从本地读取图片或从摄像头实时采集帧,OpenCV 默认以BGR 格式存储图像(这是 OpenCV 最容易踩的坑);
- 图像在内存中是一个三维 numpy 数组:
[高度, 宽度, 3],每个元素是 0-255 的整数; - 必选预处理:做高斯模糊,消除图像传感器的杂色噪点,避免出现大量细碎的假色块;
img = cv2.GaussianBlur(img, (5, 5), 0) # 5x5高斯核,标准差0
2.2 颜色空间转换
- 把每一个像素的 BGR 值转换成 HSV 值;
- 这一步是逐像素数学计算,现代 CPU 有 SIMD 加速,几乎不耗时;
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) # 注意是BGR转HSV,不是RGB
2.3 阈值分割(最核心步骤)
- 对每个像素,检查它的 H、S、V 值是否同时落在你预先设置的颜色阈值范围内;
- 符合:标记为白色(255,前景)
- 不符合:标记为黑色(0,背景)
- 输出一张只有黑白两色的 "二值化掩码图像",此时目标色块已经变成了白色的区域;
示例:红色的典型 OpenCV 阈值
# 红色在色相环两端(0°和180°重合),所以必须分两段设置
lower_red1 = np.array([0, 40, 40]) # H下限, S下限, V下限
upper_red1 = np.array([10, 255, 255]) # H上限, S上限, V上限
lower_red2 = np.array([170, 40, 40])
upper_red2 = np.array([180, 255, 255])
# 生成掩码并合并两个红色范围
mask1 = cv2.inRange(hsv, lower_red1, upper_red1)
mask2 = cv2.inRange(hsv, lower_red2, upper_red2)
mask = cv2.bitwise_or(mask1, mask2)
2.4 形态学处理
对二值化掩码做 "膨胀" 和 "腐蚀" 的组合操作:
- 先腐蚀:消除小的白色噪点,断开相邻色块的微弱连接;
- 后膨胀:把目标色块的边缘补完整,填补内部的小黑洞;
这一步能大幅减少误识别,让色块轮廓更清晰:
kernel = np.ones((5, 5), np.uint8) # 5x5结构元素
mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel) # 开运算=腐蚀+膨胀,去噪
mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel) # 闭运算=膨胀+腐蚀,补洞
2.5 轮廓查找与特征分析(找到真正的色块)
- 遍历二值化掩码,找到所有白色区域的外边界(轮廓);
- 对每个轮廓计算它的基本特征,然后根据条件筛选出符合要求的色块;
# 寻找所有最外层轮廓,压缩轮廓点节省内存
contours, hierarchy = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# 遍历所有轮廓,筛选出符合条件的色块
for cnt in contours:
area = cv2.contourArea(cnt) # 计算轮廓包围的像素面积
if 200 < area < 10000: # 过滤掉太小的噪点和太大的背景
x, y, w, h = cv2.boundingRect(cnt) # 计算外接矩形
cx = x + w // 2 # 计算中心X坐标
cy = y + h // 2 # 计算中心Y坐标
aspect_ratio = w / h # 计算长宽比
# 绘制识别结果
cv2.rectangle(img, (x, y), (x+w, y+h), (0, 255, 0), 2)
cv2.circle(img, (cx, cy), 5, (0, 0, 255), -1)
三、OpenCV 色块识别的 "铁三角" 函数到底做了什么
几乎所有 OpenCV 色块识别代码,不管封装得多复杂,本质上都是在调用这三个核心函数的组合:
cv2.cvtColor(..., cv2.COLOR_BGR2HSV):完成步骤 2 的颜色空间转换,是色块识别的基础;cv2.inRange(...):完成步骤 3 的阈值分割,是整个算法最核心的部分,直接决定识别准确率;cv2.findContours(...):完成步骤 5 的轮廓查找,把像素点转换成可分析的色块对象;
四、完整可运行的 OpenCV 色块识别代码
import cv2
import numpy as np
# 红色阈值(OpenCV标准范围)
lower_red1 = np.array([0, 40, 40])
upper_red1 = np.array([10, 255, 255])
lower_red2 = np.array([170, 40, 40])
upper_red2 = np.array([180, 255, 255])
# 打开摄像头
cap = cv2.VideoCapture(0)
while True:
ret, img = cap.read()
if not ret:
break
# 1. 预处理:高斯模糊去噪
img_blur = cv2.GaussianBlur(img, (5, 5), 0)
# 2. 颜色空间转换
hsv = cv2.cvtColor(img_blur, cv2.COLOR_BGR2HSV)
# 3. 阈值分割生成掩码
mask1 = cv2.inRange(hsv, lower_red1, upper_red1)
mask2 = cv2.inRange(hsv, lower_red2, upper_red2)
mask = cv2.bitwise_or(mask1, mask2)
# 4. 形态学处理
kernel = np.ones((5, 5), np.uint8)
mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel)
mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel)
# 5. 查找轮廓并筛选
contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
for cnt in contours:
area = cv2.contourArea(cnt)
if 200 < area < 10000:
x, y, w, h = cv2.boundingRect(cnt)
cx = x + w // 2
cy = y + h // 2
# 绘制结果
cv2.rectangle(img, (x, y), (x+w, y+h), (0, 255, 0), 2)
cv2.putText(img, f"Red: ({cx},{cy})", (x, y-10),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)
# 显示结果
cv2.imshow("Original", img)
cv2.imshow("Mask", mask)
# 按ESC退出
if cv2.waitKey(1) == 27:
break
cap.release()
cv2.destroyAllWindows()
五、常见问题的 OpenCV 底层原理
- 光线变化影响识别:主要影响 V(明度)通道,解决方法是把 V 的下限调低(比如从 40 调到 20),或者使用自动曝光;
- 反光导致识别失败:反光处的 S(饱和度)会骤降到接近 0,解决方法是把 S 的下限调低,或者在镜头前加偏振镜;
- 两个色块靠太近被识别成一个:因为它们的白色区域连在一起了,
findContours会把它们当成一个轮廓,解决方法是增加一次腐蚀操作,或者调小面积阈值; - 有很多小噪点:增大形态学处理的 kernel 大小(比如从 5x5 改成 7x7),或者提高面积筛选的下限;
- 颜色相近的物体被误识别:缩小 H 通道的范围,同时提高 S 通道的下限,只保留最纯的目标颜色;
更多推荐


所有评论(0)