1. 项目缘起:为什么颜色识别值得单独拿出来做?
做图像处理的朋友,对OpenCV肯定不陌生。从人脸识别到目标检测,从图像滤波到特征匹配,OpenCV几乎成了我们工具箱里的“瑞士军刀”。但不知道你有没有发现,很多看起来很酷的项目,第一步往往是从一个最基础的需求开始的:识别画面里某个特定颜色的东西。比如,让机器人追踪一个红色的球,让产线分拣蓝色的零件,或者从监控视频里找出穿黄色衣服的人。
这就是“多种颜色识别”这个项目看似简单,却极具实战价值的原因。它不是一个孤立的算法演示,而是一个通向更复杂视觉应用的“基石”。很多新手在学OpenCV时,会跟着教程把边缘检测、轮廓查找都跑一遍,但一到自己动手做一个完整的、能适应不同环境的颜色识别程序时,就卡壳了。问题出在哪?不是API不会用,而是对颜色空间的理解、对噪声的处理、对参数动态调整的策略没有形成一个闭环。
我见过不少项目,在实验室的纯色背景下识别率高达99%,一到自然光下或者换个摄像头,就彻底“瞎”了。这背后的核心,绝不仅仅是调用一下cv2.inRange()那么简单。它涉及到你如何理解颜色、如何与变幻莫测的光照条件“斗智斗勇”、如何设计一个既准确又高效的识别流程。所以,这个实战项目,我们不打算只给出一段代码,而是想带你走一遍从原理到坑点,再到优化策略的完整路径,让你真正掌握这项看似基础,实则暗藏玄机的技能。
2. 核心原理:颜色空间与阈值分割的“道”与“术”
在开始写代码之前,我们必须先统一思想:计算机“看到”的颜色和我们人眼感知的颜色,不是一回事。我们觉得一个东西是“红色”,可能是因为它在阳光下显得鲜艳,在阴影下显得暗沉,但大脑会自动把它归类为“红色”。计算机没有这种高级的认知能力,它只认识数字。因此,我们需要一个数学模型来定量描述颜色,这就是颜色空间。
2.1 为什么RGB空间不适合直接做颜色识别?
最直观的颜色空间是RGB(红、绿、蓝)。一个像素由三个通道的值组成,比如纯红色是 (255, 0, 0)。看起来很简单,对吧?但如果你尝试用RGB空间来识别红色物体,很快就会遇到麻烦。
想象一个红色的苹果。在正午的阳光下,它的RGB值可能是 (220, 50, 50);到了傍晚,光线变暗且偏黄,它的RGB值可能变成了 (180, 70, 60);如果它有一部分处于阴影中,RGB值可能进一步降到 (100, 30, 25)。你会发现,虽然在我们看来它都是“红色”,但它的R、G、B三个值都在剧烈变化,而且变化没有统一的规律。如果你设定一个固定的阈值范围,比如 R > 150 且 G < 100 且 B < 100,那么在强光下可能识别成功,在弱光或阴影下就会漏检。
根本原因在于,RGB颜色空间将亮度信息和颜色信息耦合在了一起。当光照强度变化时,R、G、B三个通道会同比例增减,导致颜色值在三维空间中沿着一条斜线移动,而不是稳定在一个点附近。这使得基于固定阈值的分割变得极其脆弱。
2.2 HSV/HSL空间:将亮度与颜色“解耦”的利器
为了解决RGB空间的问题,我们引入了HSV(色相、饱和度、明度)或HSL(色相、饱和度、亮度)颜色空间。这是本项目成功的关键。
- 色相 (Hue):表示颜色的种类,如红、黄、绿、蓝。它用一个角度(0-360度)来表示,形成一个色环。红色通常在0度或180度附近(取决于具体实现)。关键点在于,色相在很大程度上与光照强度无关。一个红色的物体,无论它是亮是暗,只要颜色没变,其色相值就相对稳定。
- 饱和度 (Saturation):表示颜色的纯度或鲜艳程度。饱和度越高,颜色越纯;饱和度越低,颜色越接近灰色。
- 明度/亮度 (Value/Lightness):表示颜色的明亮程度。这个通道直接受光照影响。
OpenCV中默认的HSV范围是:H: 0-179, S: 0-255, V: 0-255。注意,色相H被压缩到了0-179(即360度的一半),这是为了用一个字节(8位)来存储,提高计算效率。所以,当我们说“红色在0度附近”时,在OpenCV里对应的H值大约是0-10以及170-179(因为红色在色环的两端)。
转换到HSV空间后,我们的识别策略就清晰了:主要依据色相(H)通道来锁定颜色类型,用饱和度(S)来过滤掉灰白等不鲜艳的区域,用明度(V)来排除过暗或过曝的区域。这样,我们就构建了一个对光照变化相对鲁棒的识别模型。
2.3 阈值分割:从理论到实践的桥梁
理解了HSV,下一步就是阈值分割,也就是cv2.inRange()函数所做的事情。这个函数接受一个图像(或图像数组)和上下限阈值,输出一个二值图像(掩膜),其中在阈值范围内的像素点为白色(255),之外的为黑色(0)。
import cv2 import numpy as np # 假设我们已将图像从BGR转换到HSV hsv_image = cv2.cvtColor(image, cv2.COLOR_BGR2HSV) # 定义想要识别的颜色范围(例如:红色) # 注意:红色在HSV色环的两端,所以需要两个范围 lower_red1 = np.array([0, 70, 50]) # H低,S低阈值,V低阈值 upper_red1 = np.array([10, 255, 255]) lower_red2 = np.array([170, 70, 50]) upper_red2 = np.array([180, 255, 255]) # 生成掩膜 mask1 = cv2.inRange(hsv_image, lower_red1, upper_red1) mask2 = cv2.inRange(hsv_image, lower_red2, upper_red2) red_mask = cv2.bitwise_or(mask1, mask2)这里有几个参数需要你根据实际情况调整:
- H通道范围:这是核心,决定了识别哪种颜色。你需要通过工具(后面会讲)预先确定目标颜色的H值大致范围。
- S通道下限:用来过滤掉饱和度低的区域(接近白色、灰色、黑色)。设置得太高可能会丢失颜色较淡的目标;设置得太低则容易把灰度噪声也识别进来。一般从50-100开始尝试。
- V通道下限:用来过滤掉太暗的区域。同样需要根据环境光照调整。
实操心得一:阈值的“宽严”哲学阈值范围不是越精确越好。太窄(例如H范围只有5个像素)会导致识别不稳定,目标稍有颜色偏差或光照变化就会丢失。太宽(例如H范围横跨60度)则会把很多不相关的颜色也识别进来,引入噪声。我的经验是,在保证不引入大量错误识别的前提下,适当放宽H范围(特别是对于光照不均的场景),同时利用S和V通道进行辅助约束,是更稳健的策略。这好比抓鱼,网眼大小(H范围)要合适,还要选择在水深(V)合适、水质清澈(S)的地方下网。
3. 实战构建:一个可交互的多颜色识别程序
理论清楚了,我们开始动手搭建。我们的目标是创建一个程序,不仅能识别预设的几种颜色(红、绿、蓝),还能通过简单的交互方式动态调整阈值,并实时显示识别结果和统计信息。
3.1 环境准备与基础框架
首先,确保你的环境已经安装了OpenCV和NumPy。
pip install opencv-python numpy我们程序的骨架如下:
import cv2 import numpy as np # 初始化摄像头 cap = cv2.VideoCapture(0) # 0代表默认摄像头 # 定义初始颜色阈值(HSV格式) # 我们将颜色阈值存储在一个字典中,方便管理和扩展 color_thresholds = { 'red': {'lower': [np.array([0, 70, 50]), np.array([10, 255, 255])], 'upper': [np.array([170, 70, 50]), np.array([180, 255, 255])]}, 'green': {'lower': np.array([40, 70, 50]), 'upper': np.array([80, 255, 255])}, 'blue': {'lower': np.array([100, 70, 50]), 'upper': np.array([130, 255, 255])}, } # 为每种颜色分配一个显示用的BGR颜色 color_display = { 'red': (0, 0, 255), 'green': (0, 255, 0), 'blue': (255, 0, 0), } while True: # 读取一帧 ret, frame = cap.read() if not ret: break # 将帧从BGR转换到HSV颜色空间 hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV) # 创建一个全黑的掩膜,用于叠加所有识别结果 combined_mask = np.zeros(frame.shape[:2], dtype=np.uint8) # 用于在屏幕上显示检测结果的列表 detected_info = [] # 遍历每种颜色进行识别 for color_name, thresholds in color_thresholds.items(): if color_name == 'red': # 红色需要两个范围 mask1 = cv2.inRange(hsv, thresholds['lower'][0], thresholds['upper'][0]) mask2 = cv2.inRange(hsv, thresholds['lower'][1], thresholds['upper'][1]) mask = cv2.bitwise_or(mask1, mask2) else: mask = cv2.inRange(hsv, thresholds['lower'], thresholds['upper']) # 形态学操作:去除小噪声,连接相邻区域 kernel = np.ones((5,5), np.uint8) mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel) # 开运算:先腐蚀后膨胀,去小白点 mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel) # 闭运算:先膨胀后腐蚀,填小黑洞 # 寻找轮廓 contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) for cnt in contours: area = cv2.contourArea(cnt) # 过滤掉太小的轮廓,避免噪声 if area > 500: # 面积阈值,可根据实际情况调整 x, y, w, h = cv2.boundingRect(cnt) # 在原图上绘制矩形框和文字 cv2.rectangle(frame, (x, y), (x+w, y+h), color_display[color_name], 2) cv2.putText(frame, f'{color_name}', (x, y-10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, color_display[color_name], 2) detected_info.append(f'{color_name}: {int(area)} pixels') # 将该颜色的掩膜叠加到总掩膜上(用于生成高亮显示图) combined_mask = cv2.bitwise_or(combined_mask, mask) # 创建一个高亮显示识别区域的图像 highlighted = frame.copy() highlighted[combined_mask == 255] = [0, 255, 255] # 将识别区域高亮为青色 # 在图像上显示检测到的信息 y_offset = 30 for info in detected_info: cv2.putText(frame, info, (10, y_offset), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 1) y_offset += 25 # 显示原图和高亮图 cv2.imshow('Original with Detection', frame) cv2.imshow('Highlighted Areas', highlighted) # 按'q'退出 if cv2.waitKey(1) & 0xFF == ord('q'): break # 释放资源 cap.release() cv2.destroyAllWindows()这段代码构建了一个基础但功能完整的识别流程:读取视频流、转换颜色空间、应用阈值、形态学处理、查找轮廓、过滤并标注。运行后,你应该能看到摄像头画面,并且红、绿、蓝色的物体会被框出来。
3.2 动态调参:让程序适应你的环境
上面的代码使用了固定的阈值。但正如我们之前讨论的,固定的阈值很难适应所有环境。因此,一个实用的颜色识别程序必须提供动态调整阈值的能力。OpenCV的cv2.createTrackbar()函数可以帮我们快速创建滑动条,实现实时调参。
我们在程序初始化部分(主循环之前)添加创建滑动条的代码:
# 创建一个窗口用于放置滑动条 cv2.namedWindow('Threshold Adjust') # 为HSV的每个通道(针对一种颜色,比如红色)创建滑动条 # 我们以调整“红色”的第一个下限阈值为例 cv2.createTrackbar('H_low', 'Threshold Adjust', 0, 179, lambda x: None) cv2.createTrackbar('S_low', 'Threshold Adjust', 70, 255, lambda x: None) cv2.createTrackbar('V_low', 'Threshold Adjust', 50, 255, lambda x: None) cv2.createTrackbar('H_high', 'Threshold Adjust', 10, 179, lambda x: None) cv2.createTrackbar('S_high', 'Threshold Adjust', 255, 255, lambda x: None) cv2.createTrackbar('V_high', 'Threshold Adjust', 255, 255, lambda x: None)然后在主循环中,读取滑动条的值,并动态更新颜色阈值字典:
# 在主循环开始处,读取滑动条值 h_low = cv2.getTrackbarPos('H_low', 'Threshold Adjust') s_low = cv2.getTrackbarPos('S_low', 'Threshold Adjust') v_low = cv2.getTrackbarPos('V_low', 'Threshold Adjust') h_high = cv2.getTrackbarPos('H_high', 'Threshold Adjust') s_high = cv2.getTrackbarPos('S_high', 'Threshold Adjust') v_high = cv2.getTrackbarPos('V_high', 'Threshold Adjust') # 更新红色阈值(这里只更新了第一个范围,第二个范围同理) color_thresholds['red']['lower'][0] = np.array([h_low, s_low, v_low]) color_thresholds['red']['upper'][0] = np.array([h_high, s_high, v_high])现在运行程序,你会看到一个额外的“Threshold Adjust”窗口,里面有六个滑动条。你可以拿一个红色的物体放在摄像头前,然后拖动滑动条,观察识别效果的变化,直到红色物体被完整、准确地识别出来,而背景干扰最小。这个过程是确定最佳阈值的黄金方法。记下此时的H、S、V上下限,它们就是你当前环境下的最优参数。
实操心得二:调参的顺序与技巧调整阈值时,不要六个滑块一起乱动。建议遵循以下顺序:
- 先调H(色相)范围:将S和V的上下限设得宽一些(例如S: 0-255, V: 0-255)。然后慢慢收窄H的范围,直到目标物体被选中,同时尽可能排除其他颜色的干扰。对于红色,你需要同时调整0-10和170-179两个范围。
- 再调S(饱和度)下限:收紧S的下限,过滤掉灰白色的背景。观察当S下限提高到多少时,目标物体边缘开始丢失?那就退回来一点,留出余量。
- 最后调V(明度)下限:收紧V的下限,过滤掉阴影等过暗的区域。同样,找到目标开始丢失的临界点,然后回退一点。
- S和V的上限通常保持255即可,除非你的目标物体是过曝的(亮到发白),这时可以适当降低V的上限。
4. 进阶优化:提升识别鲁棒性与实用性
基础功能实现了,但一个健壮的项目还需要考虑更多。下面我们探讨几个常见的进阶优化点。
4.1 形态学处理的“组合拳”
在上面的代码中,我们使用了开运算和闭运算。这是形态学处理中最常用的组合,俗称“开闭运算”。
- 开运算 (MORPH_OPEN):先腐蚀后膨胀。它的作用是消除细小的亮点(噪声),断开狭窄的连接。适用于去除掩膜中因反光或噪点产生的细小白色斑点。
- 闭运算 (MORPH_CLOSE):先膨胀后腐蚀。它的作用是填充小的黑洞,连接邻近的白色区域。适用于填补目标物体内部因颜色不均产生的黑色空隙,或者将靠得很近的同一物体连接起来。
内核大小kernel = np.ones((5,5), np.uint8)中的(5,5)是一个关键参数。数字越大,处理力度越强。如果噪声点很大,或者需要填补的缝隙很宽,就需要更大的内核。但内核太大会导致目标形状失真。通常从 (3,3) 或 (5,5) 开始尝试。
# 更精细的形态学处理示例 kernel_small = np.ones((3,3), np.uint8) kernel_medium = np.ones((5,5), np.uint8) kernel_large = np.ones((7,7), np.uint8) # 可以先开运算去小噪点,再用稍大的核做闭运算连接区域 mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel_small) mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel_medium)4.2 轮廓分析与过滤策略
找到轮廓 (cv2.findContours) 后,我们简单地用面积 (cv2.contourArea) 进行了过滤。在实际项目中,你可以引入更多几何特征来提升准确性:
- 宽高比:如果你的目标物体大致是圆形的(如球),那么它的外接矩形的宽高比应该接近1。如果是细长的物体,宽高比会很大或很小。利用这一点可以过滤掉形状不符的噪声。
aspect_ratio = w / float(h) if 0.8 < aspect_ratio < 1.2: # 假设目标接近圆形 # 保留该轮廓 - 轮廓近似与圆度:使用
cv2.approxPolyDP可以简化轮廓。对于圆形物体,简化后的轮廓点数会很少。还可以计算圆度(4 * pi * area) / (perimeter * perimeter),越接近1说明越圆。 - 轮廓层级:
cv2.findContours的RETR_EXTERNAL参数只检索最外层的轮廓,忽略嵌套在内部的轮廓。如果你的目标物体内部可能有空洞,并且你需要检测这个空洞,可以使用RETR_TREE来获取完整的轮廓层级关系。
4.3 光照补偿与白平衡
这是颜色识别在复杂环境中最大的挑战。当环境光色温变化时(比如从日光灯切换到白炽灯),整个画面的颜色都会整体偏移,可能导致你的固定阈值失效。
一种简单有效的实时补偿方法是自动白平衡。思路是假设整个场景的平均颜色应该是灰色的(即R、G、B通道的均值应该相等)。我们可以计算当前帧的RGB均值,然后调整每个通道的增益,使其达到平衡。
def auto_white_balance(image): """ 简单的灰度世界自动白平衡算法 """ # 计算每个通道的平均值 avg_b = np.mean(image[:,:,0]) avg_g = np.mean(image[:,:,1]) avg_r = np.mean(image[:,:,2]) # 计算平均灰度值 avg_gray = (avg_b + avg_g + avg_r) / 3.0 # 计算每个通道的增益 gain_b = avg_gray / avg_b gain_g = avg_gray / avg_g gain_r = avg_gray / avg_r # 应用增益,并确保值在0-255之间 balanced = image.copy().astype(np.float32) balanced[:,:,0] = balanced[:,:,0] * gain_b balanced[:,:,1] = balanced[:,:,1] * gain_g balanced[:,:,2] = balanced[:,:,2] * gain_r balanced = np.clip(balanced, 0, 255).astype(np.uint8) return balanced # 在主循环中,在转换到HSV之前,先进行白平衡处理 frame_balanced = auto_white_balance(frame) hsv = cv2.cvtColor(frame_balanced, cv2.COLOR_BGR2HSV)这个方法(灰度世界算法)计算量小,能有效应对全局的光照色偏。但对于局部强光或阴影效果有限。更高级的方法还可以考虑使用直方图均衡化、同态滤波等。
4.4 多颜色识别与冲突处理
当画面中同时存在多种颜色,且它们靠得很近甚至部分重叠时,我们的简单循环遍历方法可能会给同一个区域打上多个颜色的标签,或者在叠加掩膜时产生混乱。
解决思路是引入一个“投票”或“优先级”机制。一种方法是先为每个像素计算它属于每种颜色的“置信度”(例如,通过计算HSV值到该颜色阈值中心点的距离),然后只取置信度最高的那种颜色作为该像素的最终标签。
# 假设我们已经有了 red_mask, green_mask, blue_mask # 创建一个空的彩色标签图,初始为黑色(0表示背景) height, width = frame.shape[:2] color_label_map = np.zeros((height, width, 3), dtype=np.uint8) # 根据掩膜给标签图上色(这里简化处理,后识别的颜色会覆盖先识别的) color_label_map[red_mask == 255] = [0, 0, 255] # 红色 color_label_map[green_mask == 255] = [0, 255, 0] # 绿色 color_label_map[blue_mask == 255] = [255, 0, 0] # 蓝色 # 但是,如果一个像素同时在红色和绿色掩膜中,它会被赋予蓝色(最后执行)? # 这显然不对。我们需要处理冲突。 # 更合理的方法:计算每个像素到各颜色HSV中心点的“距离” def get_color_distance(hsv_pixel, color_hsv_center): # 简单起见,这里只计算H通道的环形距离(因为0和180接近) dh = min(abs(hsv_pixel[0] - color_hsv_center[0]), 180 - abs(hsv_pixel[0] - color_hsv_center[0])) ds = abs(hsv_pixel[1] - color_hsv_center[1]) dv = abs(hsv_pixel[2] - color_hsv_center[2]) # 加权求和作为总距离,H的权重可以高一些 distance = 0.7 * dh + 0.15 * ds + 0.15 * dv return distance # 定义各颜色的HSV中心点(示例值) color_centers = { 'red': (5, 200, 200), # 注意红色有两个中心,这里取一个近似 'green': (60, 200, 200), 'blue': (120, 200, 200), } # 对每个非零掩膜像素,计算距离并分配标签(计算量较大,可优化) # 这里仅展示思路,实际应用可能需要降采样或使用其他优化方法。对于实时性要求高的场景,这种逐像素计算的方法可能太慢。一个折中的方案是:在生成各颜色掩膜后,先进行“与”操作找出冲突区域,然后在冲突区域采用更简单的规则,比如面积优先(哪个颜色的连通域面积大就归谁),或者直接标记为“未知颜色”。
5. 从项目到产品:工程化思考与扩展方向
当你把上面的代码都跑通,并且能稳定识别出几种颜色后,这个“项目”就算成功了。但如果想把它变成一个更“产品化”的工具,或者集成到更大的系统中,还有一些事情需要考虑。
5.1 参数持久化与加载
你不能每次启动程序都手动调一遍滑动条。最佳实践是将调试好的阈值参数保存到文件(如JSON、YAML或INI格式),程序启动时自动加载。
import json def save_thresholds(filename, thresholds): """保存阈值到JSON文件""" # 需要将numpy数组转换为Python列表以便JSON序列化 serializable = {} for color, bounds in thresholds.items(): if color == 'red': serializable[color] = { 'lower': [bounds['lower'][0].tolist(), bounds['lower'][1].tolist()], 'upper': [bounds['upper'][0].tolist(), bounds['upper'][1].tolist()] } else: serializable[color] = { 'lower': bounds['lower'].tolist(), 'upper': bounds['upper'].tolist() } with open(filename, 'w') as f: json.dump(serializable, f, indent=4) def load_thresholds(filename): """从JSON文件加载阈值""" with open(filename, 'r') as f: data = json.load(f) thresholds = {} for color, bounds in data.items(): if color == 'red': thresholds[color] = { 'lower': [np.array(bounds['lower'][0]), np.array(bounds['lower'][1])], 'upper': [np.array(bounds['upper'][0]), np.array(bounds['upper'][1])] } else: thresholds[color] = { 'lower': np.array(bounds['lower']), 'upper': np.array(bounds['upper']) } return thresholds # 使用示例 # 调试好后保存 # save_thresholds('color_thresholds.json', color_thresholds) # 程序启动时加载 # color_thresholds = load_thresholds('color_thresholds.json')5.2 性能优化:速度就是一切
对于视频流处理,帧率(FPS)至关重要。如果你的处理流程太慢,识别结果会有严重延迟。以下是一些优化技巧:
- 降低分辨率:不是所有应用都需要1080p的图像。使用
cv2.resize()将图像缩小到合适的尺寸(如640x480),可以极大减少后续所有操作的像素计算量。frame_small = cv2.resize(frame, (640, 480)) hsv = cv2.cvtColor(frame_small, cv2.COLOR_BGR2HSV) # 注意:后续绘制矩形时,坐标需要按比例映射回原图尺寸 - 定义ROI(感兴趣区域):如果你的目标只可能出现在画面的某个区域(比如流水线的传送带部分),那么只对这个区域进行处理,可以节省大量时间。
roi = frame[y1:y2, x1:x2] # 定义ROI hsv_roi = cv2.cvtColor(roi, cv2.COLOR_BGR2HSV) # 在ROI上进行处理... # 绘制结果时,坐标需要加上偏移量 (x1, y1) cv2.rectangle(frame, (x1+rx, y1+ry), (x1+rx+rw, y1+ry+rh), color, 2) - 优化循环与操作:避免在Python层进行逐像素的循环。OpenCV的函数(如
inRange,bitwise_and)都是高度优化的C++实现,应尽量使用这些向量化操作。像前面提到的多颜色冲突处理的逐像素计算,就是性能瓶颈,需要谨慎使用或寻找近似算法。
5.3 扩展方向:不止于识别
颜色识别本身是一个强大的工具,但结合其他技术,它能发挥更大的作用:
- 颜色追踪:在连续帧中,通过颜色识别找到目标后,可以计算其质心。在下一帧,可以在上一帧质心周围的一个小区域(ROI)内进行搜索,这称为“均值漂移”或“CAMShift”追踪算法的简化版。这比在全图搜索要快得多。
- 形状与颜色结合识别:先通过颜色分割出候选区域,再对这些区域的轮廓进行形状分析(比如判断是否是圆形、矩形)。这样可以精确识别出“红色的圆形物体”或“蓝色的方形标签”,大大降低误报。
- 与深度学习结合:对于颜色多变、纹理复杂的目标,纯阈值方法可能力不从心。你可以用颜色识别作为预处理步骤,快速筛选出可能包含目标的区域(候选框),然后将这些区域送入一个轻量级的深度学习分类网络(如MobileNet)进行细分类。这种“传统视觉+AI”的混合架构,在嵌入式设备(如树莓派、Jetson Nano)上非常实用。
回过头看,这个“多种颜色识别”项目,就像练武中的扎马步。它训练的是你对颜色空间的理解、对图像预处理(滤波、形态学)的掌握、对参数调试的耐心,以及解决光照干扰问题的思维。这些技能,在你未来接触目标检测、图像分割、SLAM等更高级的计算机视觉任务时,会成为你坚实的地基。下次当你在复杂的项目中需要快速定位一个彩色标志物,或者为你的机器人加上“视觉”时,你会感谢今天耐心调试每一个HSV滑块的自己。