☰
基于OpenCV HSV颜色空间与轮廓检测的交通信号灯识别实战
2026/10/2 9:06:52 网站建设 项目流程

简介:针对交通信号灯检测任务,OpenCV与Python的组合能快速完成图像预处理、颜色特征提取与目标识别。这套项目源码正是面向相关学习者和开发者的实战资料,包含可直接运行的检测算法,方便理解红黄绿信号灯识别中的颜色过滤、形态学处理与轮廓筛选等关键环节。压缩包共143个文件,其中140张jpg构成真实交通场景图像集,2个py为检测主程序与辅助脚本,1个md为说明文档,整体大小19.84MB,结构简洁,便于对照数据和代码进行调试。目前已有114人学习下载,可作为入门智能交通视觉方向的参考项目。结合完整源码与图像样本,读者可以复现信号灯检测全过程,并针对不同光照、角度或状态调整阈值与参数;也能从代码组织与实验结果中,掌握算法从搭建、测试到优化的完整思路,适合课程设计、毕业设计或算法研究起步阶段使用。

1. 信号灯检测项目:经典OpenCV方案为什么到现在还有实用价值

交通信号灯检测在计算机视觉里是个“看着简单、落地全是坑”的方向。拿到这个项目标题时我第一反应是:都2024年了还有人用 OpenCV + Python 的传统图像处理做信号灯检测,而不是直接上 YOLO?但真正做过路侧感知或车载项目的人都知道,信号灯有颜色、形状、位置三重硬先验,传统方案在固定机位、算力受限、需要可解释性的场景里依然很能打。这个项目恰好是一条完整的入门闭环:从视频帧读到 HSV 颜色分割,再到轮廓校验和多帧决策,几十行代码就能跑出一个可演示、可继续扩展的检测器。适合正在做课程设计、毕业设计,或者刚开始学 OpenCV 图像处理、想找一个“能跑通、能调参、能讲清楚”的实战练手项目的人。

2. 先想清楚再写码:用 HSV 而不是 RGB 做颜色判断的算法选型

2.1 RGB 下的信号灯为什么不可靠

很多第一次做这个项目的人,上来就在 BGR 空间里写死红色(0, 0, 255)、绿色(0, 255, 0),然后在真实视频里翻车。原因是信号灯的颜色不是“纯色”,而是“被光照调制后的颜色”。白天太阳直射时,红色灯罩的高光部分会发白;黄昏或背光时,红灯整体偏暗偏紫;夜间灯光过曝时,红色区域直接变成白色圆斑。这些情况下,RGB 三个通道的数值都在剧烈漂移,写死阈值完全没有泛化能力。

HSV 色彩空间把颜色拆成三个独立维度:H 是色相,S 是饱和度,V 是明度。人眼判断“这是红灯”靠的是色相,而不是明暗;HSV 刚好把“颜色本质”和“光照强度”分开,所以信号灯检测这类强颜色特征的场景,HSV 是比 RGB 稳定得多的选择。OpenCV 里cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)一行就能完成转换,但初学者最容易漏掉的是:OpenCV 的 H 通道范围是 0 到 179,不是常见的 0 到 360,很多调参失败都是因为拿着网上 0-360 的阈值直接套进来。

2.2 信号灯的三条先验:颜色、位置、形状

这个项目能脱离深度学习跑出效果,靠的不是算法多复杂,而是充分“作弊”了信号灯的物理结构。第一条先验是颜色:红灯、黄灯、绿灯在色相上天然分离,红和黄在低饱和度下会接近,但正常天气下可区分。第二条先验是位置:固定安装的相机拍到的信号灯,永远在画面的中上部,而且通常是竖直排列的三灯面板。第三条先验是形状:点亮的灯芯在画面上近似圆形。

把这三条先验逐一编码进检测流程,效果会非常明显。颜色用 HSV 阈值分割,位置用 ROI(感兴趣区域)限定,形状用轮廓的圆形度和面积过滤。这三步做完,单帧检测就能过滤掉绝大多数干扰,比如红色车尾灯、红色招牌、路边反射光斑——因为它们要么不在 ROI 内,要么形状不是圆。

我一般会建议先不写任何代码,打开一段真实路口视频,逐帧观察信号灯在画面中的分布范围和尺寸变化,再决定 ROI 和面积阈值。这个观察过程花不了十分钟,但能省掉后面大量的盲目调参时间。

2.3 什么情况下该果断放弃这套方案

经典 OpenCV 方案不是万能的,把话说在前面能帮你省时间。当信号灯在画面里占的面积太小(比如 20 像素以下),颜色分割后压根凑不出一个完整轮廓;当相机视角严重倾斜,亮灯区域变成细长椭圆甚至被遮挡;当场景有强逆光、灯罩严重脏污、雨雾天气,HSV 阈值会频繁失效。

这些场景的正确选择是换用 YOLO 这类目标检测模型。但我的建议是:即使最终要走模型路线,也值得先把 OpenCV 这套流程完整做一遍。因为它能让你理解什么是颜色空间、什么是掩码、什么是轮廓特征,这些底层手感在调试模型、分析失败样本、做模型输出后处理时全部用得上。经典方案和深度方案不是替代关系,是递进关系。

3. 手脚并用的第一版:阈值分割、形态学与轮廓校验的完整实现

3.1 环境准备与代码骨架:先跑通再看细节

这个项目涉及的操作链是:读视频帧 → 缩放/裁剪 ROI → 转 HSV → 分别生成红黄绿掩码 → 形态学清理 → 找轮廓 → 几何过滤 → 输出状态。开始动手前把环境备好,用当前主流的 OpenCV 4.x 和 Python 3.8 以上版本都可以,安装一行命令搞定。

pip install opencv-python numpy

提示:安装后先执行python -c "import cv2; print(cv2.__version__)"确认可用。遇到ModuleNotFoundError: No module named 'cv2'时,90% 的情况是当前 Python 环境装到了别的解释器里,检查一下 IDE 或终端用的是不是同一个环境。

主循环的骨架是固定的,先把它写出来,后面所有检测逻辑都在这个循环里迭代。默认参数先别追求完美,跑通优先级最高。

import cv2 import numpy as np cap = cv2.VideoCapture("traffic_light.mp4") if not cap.isOpened(): raise FileNotFoundError("无法打开视频文件,请检查路径") while True: ret, frame = cap.read() if not ret: break # 统一尺寸:降低分辨率提升处理速度,640x360 是够用的下限 frame = cv2.resize(frame, (640, 360)) # 固定机位的信号灯几乎不会出现在画面下半部分 roi = frame[0:240, :] hsv = cv2.cvtColor(roi, cv2.COLOR_BGR2HSV) # 检测逻辑写在这里,逐帧输出结果 cv2.imshow("frame", roi) if cv2.waitKey(1) & 0xFF == ord("q"): break cap.release() cv2.destroyAllWindows()

这段代码里值得注意的参数有两个。resize到 640 宽是为了同时保证检测速度和目标尺寸:信号灯在 640 宽度下通常还能占到 20 像素以上,再低就很难过滤了。roi = frame[0:240, :]是硬编码取画面上半部分,这个裁剪高度需要根据你的视频实际场景调整,如果视频里信号灯在右侧,可以再叠加横向 ROI,比如roi = frame[0:240, 320:]。

3.2 红黄绿三色分割:红色要分两段处理的坑

HSV 分割的核心是cv2.inRange,它做的事情是:把 HSV 图像中每个像素的 H、S、V 三个通道分别落在下限和上限区间内的点置为白色(255),其余置为黑色(0),输出一张二值掩码图。难点在红色的区间设定——HSV 的色相是一个圆环,红色同时横跨了 0 附近和 180 附近两端,所以一张inRange罩不住所有红色,必须分两段生成掩码再合并。

def red_mask(hsv): # H: 0-10 偏橙红,156-180 偏紫红,两段合并才完整 lower_red1 = np.array([0, 70, 100]) upper_red1 = np.array([10, 255, 255]) lower_red2 = np.array([156, 70, 100]) upper_red2 = np.array([180, 255, 255]) mask1 = cv2.inRange(hsv, lower_red1, upper_red1) mask2 = cv2.inRange(hsv, lower_red2, upper_red2) return cv2.bitwise_or(mask1, mask2) def yellow_mask(hsv): # H: 11-25 覆盖黄到橙黄,S和V 下限控制暗黄/亮黄边界 lower_yellow = np.array([11, 80, 120]) upper_yellow = np.array([25, 255, 255]) return cv2.inRange(hsv, lower_yellow, upper_yellow) def green_mask(hsv): # H: 60-90 覆盖绿到青绿,太窄会漏,太宽会混入蓝绿 lower_green = np.array([60, 60, 60]) upper_green = np.array([90, 255, 255]) return cv2.inRange(hsv, lower_green, upper_green)

这里三个函数里的 S、V 下限是最值得动手调的参数。S 下限管的是“颜色浓度”,调低能捡回暗光下的灯,但也会把灰色物体误检成目标;调高能过滤灰白噪声,但夜间红灯可能因为饱和度不足被丢掉。V 下限管“亮度门槛”,白天可以设 100 以上,夜间必须调低到 60 左右。我的调试顺序是先把 H 范围定准,然后在不同时段的视频帧上逐张对比掩码效果,最后再回来收紧 S 和 V。

3.3 形态学清理与轮廓过滤:去掉噪点,只留圆形亮斑

分割出来的掩码图往往布满了小噪点:路面反光、远处车灯的光晕、树叶缝隙里的光斑,都会在掩码图里形成零散的白色像素块。直接用cv2.findContours会找出几十个假目标。所以要在掩码图之后做一次形态学处理:先用开运算去掉孤立小点,再用闭运算把灯芯内部的细小断口填上。

def clean_mask(mask): kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5)) # 开运算:先腐蚀再膨胀,去掉背景小噪点 mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel) # 闭运算:先膨胀再腐蚀,修补灯芯内部的黑色缝隙 mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel) return mask

形态学之后进入轮廓阶段。findContours拿到所有候选轮廓后,用两个几何指标做过滤。第一个是面积:太小的是噪点,太大的是近处车灯或大面积反光。第二个是“圆形度”——用轮廓面积和最小外接圆的面积做比值,圆心正好、边缘饱满的真灯芯,圆形度通常在 0.7 以上,而车尾灯、招牌灯箱这类不规则光斑这一比值会明显偏低。

def filter_contours(mask): contours, _ = cv2.findContours( mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE ) targets = [] for cnt in contours: area = cv2.contourArea(cnt) # 面积下限 60 像素约等于直径 9 像素的圆,可根据相机距离调整 if area < 60 or area > 8000: continue # 最小外接圆 (x, y), radius = cv2.minEnclosingCircle(cnt) if radius < 3: continue # 圆形度 = 轮廓面积 / 外接圆面积,接近 1 说明越像圆 circularity = area / (np.pi * radius * radius) if circularity < 0.55: continue # 记录轮廓中心、半径和面积,供后续位置判断 M = cv2.moments(cnt) if M["m00"] == 0: continue cx = int(M["m10"] / M["m00"]) cy = int(M["m01"] / M["m00"]) targets.append((cx, cy, int(radius), area, circularity)) return targets

参数说明:RETR_EXTERNAL只取最外层轮廓,因为掩码里不会出现“轮廓套轮廓”的合法结构,用这个模式能避免内部重复轮廓参与统计;CHAIN_APPROX_SIMPLE压缩轮廓点数量,省内存和后续计算时间。圆形度阈值 0.55 是经验值,严格到 0.7 会漏掉被灯罩边缘遮挡的灯芯,放宽到 0.5 会让圆形车灯混进来,建议先按 0.55 跑一遍再看误检情况。

3.4 单帧分类:掩码叠加与位置判断

有了红黄绿三张掩码和各自的候选轮廓后,单帧分类的逻辑就很简单:分别统计三张掩码里通过过滤的候选轮廓数量,数量最多且超过最低阈值的颜色就是当前信号灯状态。这一步不做任何微妙判断,纯粹是“谁占地多谁赢”。

def classify_frame(roi, hsv): color_names = ["red", "yellow", "green"] mask_builder = { "red": red_mask, "yellow": yellow_mask, "green": green_mask, } best_color = None best_count = 0 for color in color_names: mask = clean_mask(mask_builder[color](hsv)) candidates = filter_contours(mask) # 该颜色候选亮斑数量最多,且至少 1 个有效目标 if len(candidates) > best_count: best_count = len(candidates) best_color = color if best_count == 0: return "unknown" return best_color

单帧分类能跑通,但距离工程可用还差一步:视频流的检测不能只看单帧,必须引入上下文信息。比如红灯闪烁的黄灯过渡期、车辆遮挡导致的短暂消失、算法偶发的单帧误检,如果都以单帧结果为准,输出会狂跳。多数信号灯项目的优化重点其实不在这套颜色分割本身,而在怎么把单帧结果做成一个稳定输出的状态机。这部分我放到下一章展开。

4. 视频流里的工程细节:ROI 限定与多帧投票让结果稳定可输出

4.1 固定 ROI:把检测范围压缩到信号灯实际出现的区域

上一章的代码已经在做 ROI 裁剪,但只做了“上半部分”这一层。工程化时要再往前一步:统计视频里信号灯出现的真实范围,用具体像素坐标把 ROI 锁死。固定机位的摄像头装好之后,画面里的信号灯位置几乎不变,ROI 越紧,误检越少,处理越快。

我的做法是先从视频里抽 20 到 30 帧,用图像标注工具或直接目测,记录每帧信号灯面板的左上角和右下角坐标,取并集得到 ROI。

# 假设多次观察后确定信号灯位于画面的 (380, 20) 到 (560, 180) ROI_X, ROI_Y, ROI_W, ROI_H = 380, 20, 180, 160 def extract_roi(frame): x1, y1 = ROI_X, ROI_Y x2, y2 = ROI_X + ROI_W, ROI_Y + ROI_H return frame[y1:y2, x1:x2]

ROI 定义有几个实际好处。第一,车尾灯、路边霓虹灯这些干扰源直接不在检测范围内,从源头上消灭掉一大类误检。第二,后续找轮廓时只需要处理一个几百乘几百的小图,单帧耗时能降一个量级。第三,ROI 内的坐标都是相对坐标,后续判断灯亮位置(上/中/下)时不需要关心它在全图中的绝对位置。

这里一个重要细节:ROI 裁剪要放在resize之后,不要先裁剪原图再缩放。先裁剪后缩放的缺点是 ROI 内目标可能因为原图尺寸太大被缩小到丢失,而且多一次全图级缩放开销反而更大。先统一缩放到 640 宽度,再做小区域裁剪,处理流程更顺。

4.2 三灯芯区域判定:在面板内部判断“哪个灯点亮”

单帧颜色分类能判断“画面中有红灯”,但信号灯面板里同时存在红色灯罩、黄色灯罩、绿色灯罩,白天日光下三个灯罩都会反射光线,如果只做全 ROI 的置信度比较,三张掩码都能检测到目标,分类就糊了。正确的做法是先定位信号灯面板,再在面板内部按上、中、下三个子区域分别判断哪个灯芯亮。

信号灯面板的定位在传统视觉里有多种做法,常见的有:对 ROI 做边缘检测后用霍夫直线拟合矩形边框;或者用颜色先验——绿色灯罩和红色灯罩的反光区域通常连成一个竖向条带。对一个入门项目,更稳的是直接用固定三分区:把信号灯 ROI 的高度三等分,上 1/3 查红灯,中 1/3 查黄灯,下 1/3 查绿灯。这个假设对国内绝大多数竖排信号灯成立,横排信号灯则改成左中右三分区。

def classify_by_position(hsv, roi_shape): h, w = roi_shape[:2] third = h // 3 # 三个子区域分别检查对应颜色 regions = { "red": (0, third), "yellow": (third, 2 * third), "green": (2 * third, h), } for color, (y_start, y_end) in regions.items(): sub_hsv = hsv[y_start:y_end, :] if color == "red": mask = red_mask(sub_hsv) elif color == "yellow": mask = yellow_mask(sub_hsv) else: mask = green_mask(sub_hsv) # 该子区域内有效亮斑面积是否达到点亮标准 mask = clean_mask(mask) candidates = filter_contours(mask) if candidates: return color return "unknown"

这个方案的参数是“点亮标准”。每个候选的半径、面积已经算过,filter_contours返回的列表只要非空就认为该灯亮。实际场景里可能因为雨雾或过曝导致候选丢失,更稳的写法是同时统计子区域内掩码白色像素占比,比如占比超过 0.5% 才判定为点亮。hsv[y_start:y_end, :]这种切片操作不会复制数据,只是视图,性能开销可忽略。

4.3 多帧投票:用时间维度彻底压制单帧抖动

视频检测最忌讳的就是“上一帧绿灯、这一帧 unknown、下一帧又绿灯”,输出给下游控制逻辑会产生抖动。单帧分类无论如何都会有误检和漏检,工程化的做法是用一个滑动窗口缓存最近若干帧的分类结果,只在窗口内多数帧结论一致时才改变输出。

from collections import deque class LightStateMachine: def __init__(self, window_size=10, min_ratio=0.8): self.history = deque(maxlen=window_size) self.state = "unknown" self.min_ratio = min_ratio def update(self, current_color: str): # 跳过未知帧,避免拉低有效票数 if current_color != "unknown": self.history.append(current_color) if len(self.history) < 5: return self.state # 统计窗口内出现次数最多的颜色 best = max(set(self.history), key=self.history.count) best_count = self.history.count(best) ratio = best_count / len(self.history) # 只有比例超过阈值才切换状态,否则维持原状态 if ratio >= self.min_ratio: self.state = best return self.state

两个参数各有用处。window_size=10控制投票用的历史帧数,窗口越大越稳,但状态切换延迟越高,信号灯从红变绿的那一帧,输出要滞后约 10 帧才反应过来;对一般演示项目 0.5 秒以内的延迟可接受。min_ratio=0.8意味着 10 帧里至少 8 帧同色才切换,能挡住偶发误检。如果对实时性要求高,可以把窗口缩到 5、比例降到 0.6,代价是稳定性下降。

提示:状态机还没有处理“颜色切换”的物理约束。信号灯合法切换顺序是红→绿→黄→红,不可能绿直接跳红。给状态机加一张状态转移表,拒绝非法跳变,是成本极低但非常有效的后处理过滤。

5. 参数实测与避坑清单:光照变化让阈值全面翻车的 4 条记录

5.1 红灯检测范围太窄:暗红、偏紫红全部漏检

现象:白天正对信号灯检测准确率很高,到了傍晚或阴天,红灯频繁漏检,掩码图里红色区域一片漆黑,怎么调 V 下限都没用。

原因:红色的色相范围在 HSV 圆环上被 0 度硬生生分成两段。很多人只写了H: 0-10这一段,把 156 到 180 的紫红区间漏掉了。傍晚的红灯因为大气散射偏紫红,H 值落在 160 附近,正好在漏掉的区间里。另外 V 下限设太高,也会把偏暗的红灯整体滤掉。

解决:红色掩码必须用两段inRange合并,且 V 下限降到 60 到 80 区间。我习惯在代码里把红色的两段阈值写成同一函数的开头两个数组,注释里写明“0-10 偏橙,156-180 偏紫”,防止后续维护时被删掉一段。

5.2 红色车尾灯、刹车灯导致误检

现象:视频里没有红灯,但检测结果频繁输出红色,打开调试窗口一看,ROI 边缘正好压着一辆红色轿车的尾灯,或者前车刹车灯正好在画面中上部。

原因:车尾灯和信号灯在“颜色”层面高度相似——都是高饱和度红色圆形亮斑,HSV 阈值无法区分两者。只靠颜色分割和圆形度过滤,本质上分不开它们。

解决:三条路径叠加。第一,把 ROI 缩紧到信号灯面板实际出现的区域,从源头排除路面车辆;第二,启用三灯芯位置判定,红色只在上 1/3 子区域生效,车尾灯即使进 ROI 也不会落在“红灯该在的位置”;第三,给状态机加时间约束,红灯状态至少保持 N 帧才允许切换,偶发一帧的红色候选不会被直接采信。

5.3 过曝导致颜色发白:红色变成白色,掩码直接消失

现象:正午太阳直射信号灯时,灯芯区域在画面上变成纯白亮斑,红色检测完全失效;黄昏逆光时同样出现类似问题。

原因:过曝时相机传感器像素的 RGB 各通道都接近饱和,转到 HSV 空间后饱和度 S 趋近于 0,色相 H 失去意义。inRange的 S 下限通常设为 70,过曝白斑 S 可能只有十几,直接被排除。这是 HSV 方案的固有缺陷,不是参数没调好。

解决:我的做法是检测层和解译层分离。检测层除了颜色掩码,同时用极高 V 阈值(比如 V > 230)提取“过曝亮斑”;解译层把亮斑位置和三个灯芯区域做空间匹配——亮斑如果正好落在红灯区域,且红灯区域前一状态不是绿色,就基于位置上下文判定为红灯过曝。这招不完全可靠,但能把过曝场景的召回率从 30% 拉到 80% 左右。真正彻底解决要上多帧亮度自适应,属于进阶方向。

5.4 检测帧率太低:单帧处理 50ms,视频卡成幻灯片

现象:代码能跑,但视频播放明显卡顿,CPU 占用却不怎么高,单帧耗时常卡在 50ms 以上。

原因:典型的无效开销。全图 1920x1080 的视频直接做cvtColor和三次inRange,再加上大尺寸形态学核和遍历全图轮廓,都是耗时的操作。很多人忽略的是getStructuringElement的核尺寸——7x7 的椭圆核对 1080p 掩码做开闭运算,耗时是 3x3 核的四五倍。

解决:第一,进主循环前先resize到 640 宽,耗时立刻降一半;第二,ROI 裁剪后再做 HSV 转换,避免处理整帧;第三,形态学核统一用 3x3 或 5x5,信号灯灯芯在 640 宽度下直径通常 10 像素以上,5x5 核足够;第四,findContours前确认掩码数据类型是 uint8,inRange输出自带正确类型,但bitwise_or结果偶尔会被转为 bool 数组,导致轮廓提取报错或变慢。改完这一轮,单帧处理应该能压到 10ms 以内。

5.5 黄灯误判为红色或白色:色相区间重叠的边界问题

现象:黄灯亮起时,红色掩码和黄灯掩码同时检出目标,状态机在红黄之间反复横跳;某些帧黄灯看起来接近白色,黄色掩码直接失效。

原因:黄色和橙红色的 H 边界在 10 到 25 之间紧挨着。夕阳或低色温光线下,黄色灯罩偏橙,H 值落在 15 附近,红色掩码第一段上限是 10,查边界差一点;加上黄灯在过曝时 S 掉到 60 以下,黄色掩码的 S 下限 80 把它排除,于是黄灯看起来像白色。

解决:把红黄两色的 H 边界留出缓冲带——红色第一段上限降到 8,黄色下限升到 12,中间 8 到 12 的色相谁都不认,宁判 unknown 也不误判。黄色掩码的 S 下限降到 50,V 下限降到 100,捡回偏暗偏白的黄灯。调完后建议专门截一段黄灯视频逐帧验证,这个场景是最容易漏的。

6. 从“能检测”到“敢上线”:一个可视化验证台的搭建思路

项目能跑通只是第一步,敢把检测结果拿出去演示或者继续接入控制逻辑,缺一套“证据链”。我的做法是给检测器加一个实时验证台,核心是六个 HSV 阈值滑块和一张实时掩码预览图。滑块直接绑定red_mask等函数的参数,拖动时掩码和最终检测框同步刷新,不需要改一行代码就能完成全时序的参数寻优。

cv2.createTrackbar("S_min", "control", 70, 255, lambda x: None) cv2.createTrackbar("V_min", "control", 100, 255, lambda x: None) # 每次循环读取当前滑块值作为 S/V 下限,实时观察掩码变化

有了验证台之后,用一段 5 分钟的不同时段路口视频做一次“黄金评估”:手动记录每一帧的真实灯色,跑完检测后自动比对输出,算出每一个颜色类别的准确率和召回率。我给自己定的及格线是:红绿两色召回率 95% 以上,黄色因为频率低只要求 90%,误检率允许 5% 以内——低于这个线就继续调阈值,高于这个线就可以考虑接后续逻辑。

进阶方向里性价比最高的是给检测器接一个卡尔曼跟踪:先用当前方案检测到灯芯位置,再用跟踪器预测下一帧位置,检测丢失时用预测值兜底,处理遮蔽场景。再往后就是采集大量真实红灯、黄灯、绿灯图片,做成数据集去微调一个轻量 YOLO 模型,把传统方案的检测结果当作模型输出的先验过滤条件。

我自己的习惯是在项目收尾时退回原始视频,只保留验证台和状态机,把所有魔法数字整理成一份“阈值速查表”放在代码头部注释里。这个项目难的不是算法原理,而是每个参数背后的物理场景。信号灯检测调参确实有玄学成分,但把 HSV 三个通道的物理含义吃透,把 ROI、面积、圆形度、投票窗口这些工程变量逐一独立验证,大部分“翻车”都能定位到具体原因。希望这条路能帮你少踩几个坑,早日做出一个在真实场景里也能稳定工作的信号灯检测器。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询