![[OpenCV实战] 精准颜色识别:HSV阈值调优与动态范围确定](http://pic.xiahunao.cn/yaotu/[OpenCV实战] 精准颜色识别:HSV阈值调优与动态范围确定)
1. 为什么HSV更适合颜色识别在图像处理中我们最常接触的是RGB色彩空间但RGB对光线变化非常敏感。比如同一个红色物体在强光下会变成浅红在阴影中会变成暗红RGB值差异巨大。而HSV色彩空间将颜色信息Hue、饱和度Saturation和明度Value分离使得颜色识别更加稳定。我做过一个实验用手机拍摄同一盒彩色积木分别在室内灯光和阳光下拍摄。用RGB阈值识别红色积木时阳光下的识别率只有60%而改用HSV后识别率提升到92%。这是因为HSV的H通道主要反映颜色本身受光照影响较小。import cv2 import numpy as np # 读取同一物体在不同光照下的两张图片 img1 cv2.imread(indoor.jpg) # 室内光 img2 cv2.imread(sunlight.jpg) # 阳光 # 转换为HSV hsv1 cv2.cvtColor(img1, cv2.COLOR_BGR2HSV) hsv2 cv2.cvtColor(img2, cv2.COLOR_BGR2HSV) # 提取红色区域RGB方式 lower_red_rgb np.array([150,0,0]) upper_red_rgb np.array([255,100,100]) mask_rgb1 cv2.inRange(img1, lower_red_rgb, upper_red_rgb) mask_rgb2 cv2.inRange(img2, lower_red_rgb, upper_red_rgb) # 提取红色区域HSV方式 lower_red_hsv np.array([0,100,100]) upper_red_hsv np.array([10,255,255]) mask_hsv1 cv2.inRange(hsv1, lower_red_hsv, upper_red_hsv) mask_hsv2 cv2.inRange(hsv2, lower_red_hsv, upper_red_hsv)2. 动态确定HSV阈值的实战方法2.1 鼠标采样法获取基准值网上很多教程直接给出HSV阈值但实际项目中这些固定值往往不适用。我推荐先用鼠标采样法获取基准值def get_hsv_value(image_path): img cv2.imread(image_path) hsv cv2.cvtColor(img, cv2.COLOR_BGR2HSV) def mouse_callback(event, x, y, flags, param): if event cv2.EVENT_LBUTTONDOWN: print(fHSV值: {hsv[y,x]} 坐标: ({x},{y})) cv2.namedWindow(image) cv2.setMouseCallback(image, mouse_callback) while True: cv2.imshow(image, img) if cv2.waitKey(1) 0xFF ord(q): break cv2.destroyAllWindows() # 使用示例 get_hsv_value(target_object.jpg)操作建议在目标物体上多点采样特别是颜色不均匀的区域记录10-15个采样点的HSV值排除明显异常值如反光点2.2 自动计算动态阈值范围采样完成后我们需要根据这些样本确定合理的阈值范围。我的经验公式是H范围[平均值 - 15, 平均值 15]对红色需要特殊处理因为H通道是环形S范围[最小值 - 30, 255]饱和度下限适当放宽V范围[最小值 - 40, 255]明度下限要更宽松samples np.array([[5,200,220], [8,210,215], [6,195,230]]) # 示例采样数据 h_mean np.mean(samples[:,0]) h_range 15 # H通道允许波动范围 lower np.array([ max(0, h_mean - h_range), # H最小值 max(0, np.min(samples[:,1]) - 30), # S最小值 max(0, np.min(samples[:,2]) - 40) # V最小值 ]) upper np.array([ min(179, h_mean h_range), # H最大值 255, # S最大值 255 # V最大值 ]) print(f推荐阈值范围:\n下限: {lower}\n上限: {upper})3. 处理特殊颜色情况的技巧3.1 红色物体的特殊处理红色在HSV色环中位于0°和180°附近需要特殊处理。我通常采用双区间法# 处理红色阈值 lower_red1 np.array([0, 100, 100]) upper_red1 np.array([10, 255, 255]) lower_red2 np.array([170, 100, 100]) upper_red2 np.array([180, 255, 255]) mask_red1 cv2.inRange(hsv_img, lower_red1, upper_red1) mask_red2 cv2.inRange(hsv_img, lower_red2, upper_red2) mask_red cv2.bitwise_or(mask_red1, mask_red2)3.2 应对反光和阴影强反光会导致S值降低阴影会使V值降低。我常用的解决方案是对高反光区域适当提高S下限对阴影区域降低V下限但增加形态学处理# 抗反光处理 lower np.array([h_mean-10, 150, 50]) # 提高S下限 upper np.array([h_mean10, 255, 255]) mask cv2.inRange(hsv_img, lower, upper) kernel np.ones((5,5), np.uint8) mask cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel) # 闭运算填充空洞4. 完整工作流与性能优化4.1 自适应阈值调整流程基于项目经验我总结出以下工作流初始采样用鼠标采样法获取基准HSV值自动计算根据样本计算初始阈值范围实时调整通过滑动条微调参数效果验证在不同光照条件下测试最终确定保存最优参数组合def adjust_thresholds(image_path): img cv2.imread(image_path) hsv cv2.cvtColor(img, cv2.COLOR_BGR2HSV) # 创建调整窗口 cv2.namedWindow(thresholds) # 初始化阈值 init_h, init_s, init_v 30, 150, 150 cv2.createTrackbar(H min, thresholds, init_h-20, 179, lambda x: None) cv2.createTrackbar(H max, thresholds, init_h20, 179, lambda x: None) cv2.createTrackbar(S min, thresholds, init_s-50, 255, lambda x: None) cv2.createTrackbar(V min, thresholds, init_v-50, 255, lambda x: None) while True: # 获取当前滑动条位置 h_min cv2.getTrackbarPos(H min, thresholds) h_max cv2.getTrackbarPos(H max, thresholds) s_min cv2.getTrackbarPos(S min, thresholds) v_min cv2.getTrackbarPos(V min, thresholds) # 应用阈值 lower np.array([h_min, s_min, v_min]) upper np.array([h_max, 255, 255]) mask cv2.inRange(hsv, lower, upper) # 显示结果 result cv2.bitwise_and(img, img, maskmask) cv2.imshow(result, result) if cv2.waitKey(1) 0xFF ord(q): break cv2.destroyAllWindows() return lower, upper4.2 性能优化建议在机器人分拣等实时场景中我通常会做以下优化缩小处理区域先检测物体大致位置只在该区域进行颜色识别降低分辨率在不影响识别的前提下适当缩小图像尺寸并行处理对多颜色目标使用多线程并行处理不同颜色通道硬件加速启用OpenCV的IPP或CUDA加速# 区域限制示例 def detect_in_roi(img, roi, color_thresholds): x,y,w,h roi roi_img img[y:yh, x:xw] hsv cv2.cvtColor(roi_img, cv2.COLOR_BGR2HSV) mask cv2.inRange(hsv, color_thresholds[0], color_thresholds[1]) contours, _ cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) return contours