简介面向计算机视觉初学者的一份基于颜色图像分割的OpenCV代码包涵盖阈值分割、色彩空间转换、聚类等多种常见思路旨在解决如何利用像素颜色信息将图像划为不同区域的基础问题。内容系统覆盖色彩空间转换、阈值分割、K-means聚类、混合高斯模型、边缘检测等经典方法通过可运行示例展示从RGB到HSV/YCrCb的变换、inRange颜色范围选取、聚类分割及形态学后处理等完整流程。压缩包以zip格式提供大小约235KB已有733人浏览学习。代码结构清晰注释到位适合高校学生与研究者以此为模板动手复现实验并迁移到肤色检测、植被识别等真实场景示例代码可直接修改输入图像便于观察不同分割参数对结果的影响。由于平台暂未提供文件明细文件数量与具体类型未列出但包体小巧便于快速下载与本地调试。1. 基于颜色的图像分割为什么你的阈值一换环境就失灵遇到过一个典型的模拟项目X产线上要按颜色把不同型号的螺丝分到三个料盒里最初方案是先转成灰度再做阈值分割结果灯光一开一关灰度直方图的主峰就来回漂程序里那几行阈值判断成了“玄学调参”白天黑夜效果完全不同。后来换成基于颜色的图像分割用 OpenCV 把图像从 RGB 转到 HSV 再取色相范围才把问题稳住。本文要聊的就是这套方法为什么颜色分割比灰度阈值更抗光照变化HSV 三个通道的参数到底怎么标定以及在实际落地时最常踩的几个坑。适合刚接触 OpenCV 的开发者也适合正在做颜色识别选型却总被环境光折磨的从业者。2. 为什么是 HSV 而不是 RGB颜色分割的选色原理2.1 RGB 做阈值的三个软肋很多人第一次做颜色识别第一反应就是直接把 RGB 三通道各设一个上下限比如“红色就是 R150 且 G100 且 B100”。这个思路在实验室固定光源下确实能跑但换个环境马上露馅。首先是通道耦合问题RGB 三通道是高度相关的光变强时三个通道一起变大光变弱时又一起缩小你设的固定阈值等于在猜光照强度不是在做颜色判断。其次是相似色难区分暗红色和亮棕色在 RGB 空间的距离可能比暗红色跟真红色还近边界非常模糊。第三是色相信息被拆散颜色给人的感觉主要由色调决定RGB 没有显式的“色调”维度硬用三通道组合去描述它等于在做回归而不是做分类。我一般会直接跟初学者说如果目标场景里光线不是恒定的RGB 阈值方案就不值得继续调早转 HSV 早省事。2.2 HSV 的通道结构与 OpenCV 的数值范围HSV 把颜色拆成色调 H、饱和度 S、明度 V 三个独立维度这个设计的核心价值在于H 通道描述“这是什么颜色”S 描述“颜色有多纯”V 描述“整体有多亮”。当光照变化时受影响最大的是 VS 次之H 相对稳定。基于颜色的图像分割就是利用这个特性先弱化 V 的影响主要靠 H 定位目标色。这里必须提醒一个 OpenCV 特有的坑OpenCV 的 HSV 范围不是常规的 H(0-360)、S(0-100)、V(0-100)而是 H 压缩到 0-180、S 和 V 各 0-255。这个设计是为了把 H 塞进一个字节里但很多人从网上抄代码时忽略了这一点用 0-360 的值去传参数结果 mask 一片黑或一片白还以为是阈值设错了。下表是常见颜色的参考区间注意这张表只能当起点别直接当答案抄。我习惯的做法是先把目标色卡放到实际场景里拍几张图再用取色工具看 HSV 值以实测为准目标颜色H 参考范围S 参考范围V 参考范围注意事项红色0-10 与 170-18070-25560-255红色 H 在 0 附近跨越了 170-180要双区间合并绿色35-8540-25560-255和黄色接壤S 要拉高一点减少误检蓝色85-12560-25550-255深蓝和黑色的 V 很接近V 下限要压低黄色20-3580-25580-255橙色暗光下会掉进黄色区间白色0-180 全区间0-30180-255用 S 上限锁定H 基本作废2.3 这就是颜色分割的整体流程基于颜色的图像分割在 OpenCV 里的完整链路并不复杂读图 → 转 HSV → 用 inRange 生成 mask → 形态学处理去噪 → 按轮廓把目标框出来。第 3 章会把代码逐行拆开这里先把逻辑理清楚第一步是色彩空间转换为什么一定是 BGR2HSV 而不是直接对 BGR 做 inRange因为直接对 BGR 做 inRange三个通道的上下限会被光源强度整体抬升或压低而转换到 HSV 之后H 通道基本上不随亮度线性漂移分割结果才对环境光有一定抵抗力。这也是整个方法能立住的理论根基。3. 用 OpenCV 跑通第一版inRange 与 mask 的实战组合3.1 前置准备与图像读取用 Python 加 opencv-python 库这是目前最省事的组合。装库用 pip 就好版本选一个稳定的就行太新的版本偶尔会出现接口变动我一般装正式发布的稳定版。读图时有个细节OpenCV 读进来的图默认是 BGR 排列不是 RGB。这意味着你后面所有通道拆分、阈值判断都要按 BGR 的顺序来否则颜色会偏蓝偏绿肉眼看着都不对。常见做法是转 HSV 之前把这一步先写好避免后面重复踩坑。import cv2 import numpy as np # 读取图像OpenCV 默认按 BGR 三通道读入 img cv2.imread(sample.jpg) # 转 HSV注意 src 和 dst 两个参数都必须存在 hsv cv2.cvtColor(img, cv2.COLOR_BGR2HSV)这段代码里cv2.COLOR_BGR2HSV是转换标志它告诉 OpenCV 输入是 BGR 顺序输出是 HSV 空间。转换后 H、S、V 分别落在 0-180、0-255、0-255后续 inRange 的阈值全按这个范围写不要用别的软件里的 0-360 数值来套。3.2 核心分割inRange 生成 mask拿到 HSV 图之后真正干活的是cv2.inRange它把落在高低阈值之间的像素标记为白色其余标为黑色生成一张单通道的 mask 图。注意这个函数不会修改原图它只是做像素级判断。下面的例子以蓝色彩色工件为目标参数来自我实际调过的量级# 蓝色区间H 在 85-125饱和度不低于 60亮度不低于 50 lower_blue (85, 60, 50) upper_blue (125, 255, 255) # inRange 直接输出单通道 mask目标区域为白色 mask cv2.inRange(hsv, lower_blue, upper_blue)这里lower_blue和upper_blue的顺序不能写反写反的结果是除了目标区域外全白因为绝大多数像素的 HSV 值都会落在“下界高于上界”的补集里。inRange 的逻辑是lower pixel upper才设为白色所以高阈值 H 至少要比低阈值大 10 到 20 个刻度否则边缘像素会因为微小波动被排除掉mask 上出现大量碎洞。3.3 从 mask 到可视化结果位运算与原图合成mask 是一张黑白图它告诉算法“哪些像素可能是目标颜色”。但很多时候你希望直接在原图上看到分割效果或者把目标区域单独抠出来这时用cv2.bitwise_and把原图和 mask 合在一起目标区域保留原始颜色背景变黑。还有更直观的做法是把 mask 本身转成三通道再和原图叠加用轻微透明度做对比我调试时会这么做。# 把 mask 转成三通道便于和原图叠加显示 mask_bgr cv2.cvtColor(mask, cv2.COLOR_GRAY2BGR) # 原图和 mask 按位与目标区域保留原色其余为黑色 result cv2.bitwise_and(img, mask_bgr) # 叠加显示出分割范围weight 控制原图的透出程度 overlay cv2.addWeighted(img, 0.6, mask_bgr, 0.4, 0) cv2.imwrite(mask_output.png, mask) # 黑白掩膜 cv2.imwrite(result_output.png, result) # 抠出来的目标这里的addWeighted第一个 0.6 是原图权重第二个 0.4 是掩膜权重最后一个 0 是亮度偏置。权重之和不需要是 1只要想让哪边更清楚就把哪边权重调大。我一般会把 mask、result、overlay 三张图同时保存下来放在同一个文件夹里对比才能判断到底是阈值范围太窄还是形态学处理不够。3.4 形态学后处理让 mask 不再“掉渣”第一版跑出来mask 上经常是一堆碎亮点目标区域内部还有黑点这是噪声和反光导致的像素级错判。解决办法是形态学处理先用erode去掉孤立噪点再用dilate把目标区域里的洞补回来。更推荐的做法是用morphologyEx一次性做开运算或闭运算参数更清晰# 定义 5x5 的方形结构元素 kernel np.ones((5, 5), np.uint8) # 开运算先腐蚀后膨胀去掉小的白色噪点 opening cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel) # 闭运算先膨胀后腐蚀填充目标内部的黑色小洞 closing cv2.morphologyEx(opening, cv2.MORPH_CLOSE, kernel)结构元素大小直接影响处理强度。5x5 适合中等分辨率图如果 mask 噪点又密又碎试着加大到 7x7如果目标边缘细节重要就别用太大的核否则边缘会圆角化轮廓贴合度变差。这里一个容易忽略的点先开运算再闭运算的组合最适合“外部有散点、内部有小洞”的情况顺序别反反过来会把噪声先放大再腐蚀等于白做。走过这个流程之后mask 就已经具备交给轮廓分析的素质了。4. 参数别硬编码HSV 范围标定与光照变化的应对4.1 用 Trackbar 现场标定区间很多新人最头疼的就是 HSV 阈值不知道填什么只能对着像素值一点点试。这里分享一个我自己常用的标定方法写一个小窗口用 OpenCV 的createTrackbar动态调 H 上下限、S 上下限、V 上下限拖动条一变mask 实时刷新。先用这个工具找到大致范围再把数值固化到正式代码里。用 Trackbar 标定比写死数据再反复跑脚本高效得多也是我认为做颜色分割必须掌握的第一步调参技能。def nothing(x): pass cv2.namedWindow(Calibrate) cv2.createTrackbar(H_low, Calibrate, 0, 180, nothing) cv2.createTrackbar(H_high, Calibrate, 0, 180, nothing) cv2.createTrackbar(S_low, Calibrate, 0, 255, nothing) cv2.createTrackbar(S_high, Calibrate, 0, 255, nothing) cv2.createTrackbar(V_low, Calibrate, 0, 255, nothing) cv2.createTrackbar(V_high, Calibrate, 0, 255, nothing) while True: h_low cv2.getTrackbarPos(H_low, Calibrate) h_high cv2.getTrackbarPos(H_high, Calibrate) s_low cv2.getTrackbarPos(S_low, Calibrate) s_high cv2.getTrackbarPos(S_high, Calibrate) v_low cv2.getTrackbarPos(V_low, Calibrate) v_high cv2.getTrackbarPos(V_high, Calibrate) mask_cur cv2.inRange(hsv, (h_low, s_low, v_low), (h_high, s_high, v_high)) cv2.imshow(Calibrate, mask_cur) if cv2.waitKey(1) 0xFF ord(q): break cv2.destroyAllWindows()这组代码里 Trackbar 的滑动范围就按 OpenCV 的 HSV 上限来设H 最大 180S 和 V 最大 255。实际操作时我建议先把 V 的上下限拉到全开只调 H 锁定目标颜色再把 S 下限往上拉剔除偏灰偏白的伪目标最后才收 V 的范围解决过亮反光和过暗阴影的问题。这个顺序能减少一半的无谓调参时间。4.2 光照变化时的三个对策固定阈值在恒定光源下足够用但你一旦把程序搬到窗边、户外或灯光会切换的车间就一定会遇到分割区域忽大忽小的问题。常见做法有三个。第一个是输入图像先做高斯模糊半径设在 3 到 5 之间。模糊不是为了让图变好看而是抑制传感器噪声和高频反光让 HSV 值更稳定。注意只模糊原图一次模糊之后再转 HSV别在 HSV 上再模糊那样会让颜色边界润开。第二个是加一道饱和度筛选逻辑。目标颜色本身的饱和度具有极强的区分度纯色塑料工件 S 往往在 150 以上而阴影下的暗部 S 明显偏低。把 S 的下限从 60 提升到 120能直接干掉大量环境阴影和水泥地面的干扰。但代价是目标自身颜色偏浅时容易丢比如淡黄色零件 S 本来就只有 80 左右这时 S 下限要重新放宽到 50。第三个是归一化或白平衡修正。如果光源偏暖或偏冷整张图的 H 会出现系统性偏移导致 H 阈值整体失效。我一般会在读图后做一次cv2.normalize或者用灰度世界假设做简单的白平衡先把整体色偏拉回来再转 HSV。这几个方法可以组合用但注意别一下全加上否则排查问题时都不知道是哪一步把结果改坏的。4.3 从标定到曲线记住目标颜色的分布形态调参时不要只盯着“当前这张图能不能分割出来”还要看目标颜色在 HSV 三维空间里的分布形态。我用过一个快速办法对 mask 中所有像素做一次 HSV 直方图统计直接观察 H 通道的主峰位置和峰宽。如果 H 直方图只有一个尖峰阈值范围可以收紧到峰两侧各留 5-10 个刻度如果峰很宽说明目标本身颜色不均阈值范围就得放宽否则分割出的区域会稀碎且边缘不完整。# 只统计 mask 内像素的 HSV 值排除背景干扰 mask_pixels hsv[mask 0] # 统计 H 通道直方图256 个 bin hist_h cv2.calcHist([mask_pixels], [0], None, [256], [0, 256])这段代码先通过布尔索引取 mask 内的像素避免背景颜色污染直方图统计然后只对 H 通道做统计。看峰值时重点关注两点峰顶落在哪个 bin这在 OpenCV 里 0 和 180 是相邻的红色直方图会出现右端接左端的情况峰的宽度决定了阈值宽度。按这个方式标定出来的区间比你随手填一个数值要可靠得多。5. 避坑颜色分割最容易翻车的五个现场5.1 mask 全黑检查是不是 BGR 当 RGB 用现象inRange 之后 mask 里几乎没有白色区域原图中明明有大面积目标颜色。 原因读图后忘记转 HSV直接把原图 BGR 的像素值按 RGB 顺序写进阈值或者把常规 HSV 的 0-360 范围直接填给 OpenCV。 解决在 inRange 前打印一个目标像素的 HSV 值做验证用print(hsv[y, x])看一眼数据是否落在预期范围内。这个检查十秒钟能完成能排除掉七成左右的“全黑全白”问题。5.2 高光区域被识别成另一个颜色现象同一块塑料板上正常区域分割正确但高光处被排除在外有时还会被误判成白色。 原因高光区域饱和度极低同时 V 非常高颜色看起来发白。HSV 是空间里的 V 和 S 发生了变化H 反而不那么重要了。 解决把 S 的下限放宽或者直接在分割前做个高斯模糊降低反光峰值。如果反光严重到无法消除可以把 V 的上限从 255 往下收先排除最刺眼的部分。5.3 红色需要两个区间写成一个就丢一半现象红色工件识别率极低mask 只能覆盖大概一半的区域边缘还断断续续。 原因HSV 的 H 通道是环形结构红色同时分布在 0 附近的低端和 180 附近的高端。单区间逻辑只能覆盖其中一侧。 解决定义两个区间一个用(0, s_low, v_low)到(h_red, s_high, v_high)另一个用(170, s_low, v_low)到(180, s_high, v_high)两次 inRange 的结果用cv2.bitwise_or合并。这套思路对“边缘接近品红”的目标色也适用。5.4 白色和黑色被误判成背景色现象分割蓝色工件时白色背景和银色反光条也出现在 mask 里区域大小甚至比目标还大。 原因白色在 HSV 里 S 接近 0如果 S 下限设得太低H 通道会失去意义所有低饱和像素都成了候选。 解决用 S 通道做一道硬性过滤把 S 下限拉到目标颜色与白色区域的区分点之上。另外 V 的上限和下限都要合理纯白的 V 接近 255如果不需要识别白色就把 V 上限下调到 240 以内能压掉一部分高光点。5.5 视频流中 mask 闪烁抖动现象同一段视频里单帧静态分割效果很好但连续播放时目标区域大小忽大忽小边界来回跳动。 原因单帧阈值是一个硬开关像素 HSV 值只要在阈值边界附近微抖就会在 0 和 255 之间跳变产生闪烁。 解决在输出 mask 后增加时间域平滑用上一帧与当前帧的 mask 做cv2.addWeighted加权平均。也可以对轮廓结果做面积过滤只保留面积在合理范围内的连通域能滤掉大部分闪烁噪声。6. 进阶把分割结果变成真正可用的东西mask 只是半成品生产环境里你最终要拿到的是螺丝的位置、个数、框选坐标或者每个料盒的触发信号。我一般的做法是mask 做完形态学之后先找轮廓再用面积过滤掉噪声然后用外接矩形把目标框出来。下面这段代码是这套思路的精简版可以直接扩展成批量逻辑。# 找轮廓只查外层用简单逼近节约算力 contours, _ cv2.findContours(mask_final, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) boxes [] for cnt in contours: area cv2.contourArea(cnt) # 面积过滤小于 200 像素的碎片不处理 if area 200: continue x, y, w, h cv2.boundingRect(cnt) boxes.append((x, y, w, h)) cv2.rectangle(img_show, (x, y), (x w, y h), (0, 255, 0), 2)这段代码里RETR_EXTERNAL只取外轮廓避免目标内部孔洞产生重复框CHAIN_APPROX_SIMPLE减少存储点数量对实时视频流友好。面积阈值 200 需要按图像分辨率调整分辨率越高阈值越大我通常用画面总面积乘以一个比例来设定。check 一下key 是 mask 的质量mask 里有碎噪点轮廓框就必然漫天飞。所以我每次收到一张新图都会先跑一遍“三图冒烟测试”原图画线框、mask 黑白图、轮廓数量统计三张图同时摆在一起看哪一步出了问题一目了然。再补充一个批量验证习惯不要只拿一张好图调试完就收工至少准备五六张不同角度、不同光照、不同背景的图把同一套参数全部跑一遍记录每张图的误检数和漏检数。颜色分割最大的假象就是单图效果完美一换场景就翻车。从那以后我每次拿到新的颜色识别需求都强制自己先做一遍多维度的样本验证再写进正式代码这个后悔药省了我大量现场救火的时间。希望帮到你。本文还有配套的精品资源点击获取