色块识别是机器视觉中最简单也最常用的算法,核心逻辑是通过颜色阈值把目标色块从背景中分离出来,再分析分离后的像素区域轮廓特征。整个过程就像 "先按颜色挑出所有目标像素,再把连在一起的像素拼成一个个色块轮廓,最后选出符合要求的那个"。

一、核心前提:为什么不用 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 色块识别代码,不管封装得多复杂,本质上都是在调用这三个核心函数的组合:

  1. cv2.cvtColor(..., cv2.COLOR_BGR2HSV):完成步骤 2 的颜色空间转换,是色块识别的基础;
  2. cv2.inRange(...):完成步骤 3 的阈值分割,是整个算法最核心的部分,直接决定识别准确率;
  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 通道的下限,只保留最纯的目标颜色;
Logo

免费领 150 小时云算力,进群参与显卡、AI PC 幸运抽奖

更多推荐