公司动态

轨道检测实战:从Canny边缘检测到霍夫变换的轻量方案

📅 2026/9/1 11:49:20
轨道检测实战:从Canny边缘检测到霍夫变换的轻量方案
简介面向轨道检测场景的Python实现资料基于OpenCV完成铁路轨道视觉识别任务适合具备一定Python/NumPy基础、正在学习图像处理或计算机视觉的开发者。资源包共5个文件核心为py检测脚本配套requirements依赖清单与说明文档另含两段mp4轨道测试视频压缩包整体约75.68MB。代码覆盖轨道检测的主流处理链路灰度化与滤波预处理增强轨道特征Canny边缘检测定位轮廓Hough变换提取轨道线段并结合视频帧处理演示实时检测思路。随附测试视频可直接运行方便对照效果并调整阈值、ROI等参数也能借此学习OpenCV从图像读写、矩阵运算到结果可视化的完整用法。已有911人学习下载适合课堂实验、毕业设计或作为铁路视觉巡检项目的入门参考。 先说一个可能和很多人预期相反的结论轨道检测现阶段我更推荐先用传统图像处理链路打底而不是一上来就搬深度模型。原因不是深度学习不行而是“两条钢轨”这种目标的结构化程度太高了用Canny边缘检测加霍夫变换就能快速拿到一个可用的结果部署成本低、可解释性强后面真需要上模型处理复杂场景时这套链路还能当前置筛选器用完全不浪费。我会把从视频流读取、轨道线提取、干扰过滤到弯道适配的全过程拆开讲每一段代码为什么这么写、参数为什么这么选尽量说明白也会把我自己调参踩过的坑一并放进去。适合两类人看一是刚接触计算机视觉想找一个有真实落地感的练手项目二是已经在用深度学习做轨道检测但想在边缘设备上跑一个轻量前置检测方案的人。1. 为什么先别急着上深度学习轨道检测的选型思考1.1 轨道检测到底在解决什么问题在工程项目里轨道检测很少是“研究性课题”它往往嵌在具体的业务链路里面。最常见的三类落地场景轨行区安全预警摄像头装在道口或作业区边缘检测到轨道上有异物或人员越界时触发报警。巡检视频自动定位巡检车或无人机拍回来的视频需要按帧标出钢轨位置方便后续查找病害区段。轨道几何参数初筛通过图像估算轨距、轨向等量先筛出可疑区段再交给精密测量设备复核。这三类场景对“轨道检测”的要求高度一致你需要的不是一整套像素级分割结果而是在画面里把两条钢轨“定位”出来。这恰恰是传统图像处理的强项——钢轨在图像里呈现为一种非常明显的高对比度长边缘和车道线检测、路缘检测在数学上是同一个问题。1.2 传统方法凭什么能打先说一个我在项目里反复确认过的现实目标检测模型要落地你至少得先标注几千张轨道图训练完还要应付逆光、雨雪、夜间光照这些泛化问题最后部署到Jetson或者树莓派上推理速度和内存占用又得重新调一遍。而一个Canny算法在同样的设备上只要几毫秒就能跑完参数是可解释的出问题了能按链路逐步排查。当然传统方法的边界也很明显弯道上找不到直线、轨面锈蚀严重时边缘断裂、树影投在轨道上会造成大量伪边缘。这些问题我在后面的章节里会逐个给出应对手段。我的建议是在“先把项目跑起来、先拿到一个可演示的版本”这个阶段传统链路是最划算的方案没有必要为了技术上的“先进”付出十倍的成本。2. 环境准备和数据选择先把视频流跑起来2.1 环境安装与两个容易踩的坑Python环境我建议直接用较新的稳定版OpenCV装opencv-python数值计算装numpy两条命令就够pip install opencv-python pip install numpy装完以后第一件事不是写检测算法而是确认能用OpenCV正常打开你手头的视频。不要小看这一步很多项目死在最前面文件路径带中文导致打不开、视频编码格式OpenCV解不了、摄像头索引号写错。这些问题在写算法之前最好全部暴露掉。OpenCV有两个容易踩的坑。第一个是颜色通道顺序它读图默认是BGR不是RGB如果你习惯用matplotlib直接显示画面颜色会整个错乱但用cv2.imshow显示就正常。第二个是waitKey的返回值问题很多人写if cv2.waitKey(1) ord(q)发现按q退不出程序实际在多数平台上waitKey返回的是按键码的低字节需要写成if cv2.waitKey(1) 0xFF ord(q)才稳妥。2.2 第一批测试数据怎么选测试数据直接决定调试效率。第一版代码千万不要拿复杂的站场道岔视频练手那种场景下你会分不清到底是算法问题还是场景本身太难。我的建议是先用固定机位、直线段、白天顺光的视频把主链路跑通然后再换成斜光、阴影、弯道视频逐项加难度。公开的轨道视频素材其实不少行车记录仪拍的轨道交通线路画面、铁路爱好者拍摄的线路视频都可以拿来练手注意使用边界和版权就行。视频分辨率不用太高1080p足够文件也不要太大十几秒的片段足够完成第一轮验证。2.3 读取视频的骨架代码下面这段是读取视频并逐帧显示的基础骨架后面所有检测逻辑都填进process_frame函数里import cv2 def process_frame(frame): # 后续轨道检测逻辑写在这里 return frame cap cv2.VideoCapture(railway.mp4) while True: ret, frame cap.read() if not ret: break result process_frame(frame) cv2.imshow(Railway Detection, result) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()跑通这段之后你的项目就有了一个稳定输入输出框架后面所有算法改动都能在这个循环里直接看到效果。3. 直线轨道检测主链路边缘、遮罩与霍夫变换3.1 边缘检测为什么Canny对铁轨这么友好钢轨轨头在图像里是一组亮暗交替的高对比度边缘Canny算法天然适合这种目标。它基于梯度变化做非极大值抑制再用双阈值连接边缘最终能得到连续的单像素边缘线而不是像Sobel那样留下一片模糊的梯度带。钢轨的纵向边缘非常长Canny恰好能把这类长边缘保留得比较完整。Canny两个阈值的经验比值是1比2到1比3。比如低阈值45、高阈值120。如果边缘断成一截一截可以先降低低阈值试试如果整幅图都是细碎边缘说明低阈值太低或者图像噪声太大这时候把高斯模糊的核加大比反复调阈值更有效。3.2 ROI遮罩把搜索范围锁进轨道区域轨道检测里ROI是个性价比极高的操作。固定机位的摄像头钢轨位置基本稳定轨道只会出现在画面的下半部分。把上半部分直接屏蔽掉能一次性去掉天空、树木、建筑物这些干扰来源。ROI形状不用做得很精细画面下方一个梯形区域就够了覆盖轨道从近到远的透视范围。3.3 霍夫变换与方向筛选把线段变成“轨道候选线”霍夫变换是这一步的核心工具。我建议直接用HoughLinesP也就是概率霍夫变换它会直接返回线段端点的像素坐标方便后续筛选和绘制。标准霍夫变换返回的是极坐标参数虽然也能用但多一步换算没必要。参数需要理解四个threshold是投票阈值可以理解为“至少要有多少边缘像素点支持这条直线”minLineLength过滤掉太短的线段maxLineGap控制断裂边缘能连接的最大间隔铁轨边缘常被枕木缝隙打断这个值可以适当放宽。轨道线在画面里方向相对集中如果画面摆正它们接近竖直方向。用矢量角度过滤就能去掉大部分横线干扰。写代码的时候用arctan2计算线段角度再根据你的摄像头安装角度设定保留区间。3.4 完整代码主链路把上面几步串起来就是完整的直线轨道检测主链路import cv2 import numpy as np def detect_rails(image): h, w image.shape[:2] gray cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) blur cv2.GaussianBlur(gray, (5, 5), 0) edges cv2.Canny(blur, 45, 120) mask np.zeros_like(edges) roi_vertices np.array([[ (0, h), (w, h), (w, int(h * 0.55)), (0, int(h * 0.55)) ]], dtypenp.int32) cv2.fillPoly(mask, roi_vertices, 255) masked_edges cv2.bitwise_and(edges, mask) lines cv2.HoughLinesP( masked_edges, rho1, thetanp.pi / 180, threshold50, minLineLength80, maxLineGap60, ) rail_candidates [] if lines is not None: for line in lines: x1, y1, x2, y2 line[0] angle abs(np.arctan2(y2 - y1, x2 - x1) * 180 / np.pi) if 30 angle 150: length np.hypot(x2 - x1, y2 - y1) if length 60: rail_candidates.append((x1, y1, x2, y2)) return masked_edges, rail_candidates提示调试时建议把边缘图和直线图用cv2.imshow单独弹出来看不要只盯着最终结果图。这样能快速定位是预处理的问题还是霍夫参数的问题。这段代码跑完大部分直线轨道视频已经能稳定画出两条轨道线了。但再往下走你会发现真实场景远没有这么干净。4. 阴影、道砟和弯道三个绕不开的实战问题4.1 阴影与反光用HSV颜色空间做二次过滤固定机位的直线段跑通之后第一个遇到的真实问题就是阴影和反光。树影斜着投在轨道上会让边缘在阴影边界处断开轨面反光会把灰黑色的钢轨变成大面积亮白区域Canny在反光区会检出不少横向亮线。我的办法是加一道颜色辅助过滤。钢轨轨头的颜色范围其实很窄低饱和度、中等亮度。先把RGB转到HSV用inRange框出这个范围再和Canny边缘做交集相当于只保留“颜色像钢轨”的边缘hsv cv2.cvtColor(image, cv2.COLOR_BGR2HSV) lower_rail np.array([0, 0, 60]) upper_rail np.array([180, 40, 200]) rail_color_mask cv2.inRange(hsv, lower_rail, upper_rail) merged cv2.bitwise_and(masked_edges, rail_color_mask)这步不会百分百准确白色道砟、水泥平台也可能落在范围内但它能把绿色灌木、蓝色天空、黄色遮挡物大面积滤掉对后续直线检测帮助非常大。4.2 道砟纹理产生大量伪直线形态学操作救场道砟是碎石子图像里全是随机纹理Canny之后会变成密密麻麻的短线段。minLineLength能滤掉一部分但仍有不少碎线连成近似直线混进候选结果。我通常用形态学开运算去掉孤立小点再用闭运算连接断裂边缘merged cv2.morphologyEx(merged, cv2.MORPH_OPEN, np.ones((3, 3), np.uint8)) merged cv2.morphologyEx(merged, cv2.MORPH_CLOSE, np.ones((15, 15), np.uint8))开运算核用小尺寸闭运算核可以大一些但注意不要大到把两条钢轨的相邻边缘也连成一片这个参数和图像分辨率直接相关需要实测。形态学处理完之后再对候选线段按长度降序排序优先保留较长的线段然后用角度聚类选出最像轨道的两组线基本就能稳定输出左右两条轨道了。4.3 弯道场景从直线检测切换到滑动窗口拟合直线检测在弯道上会直接失灵因为钢轨不再是直线段。这时候要切换到车道线检测里非常成熟的思路先统计图像下半部分的垂直方向直方图找到两个峰值作为左右轨的起始横向位置再用滑动窗口沿纵向搜索最后用np.polyfit拟合二次曲线。核心逻辑很简单每个窗口内统计非零像素的均值位置作为下一窗口的搜索中心相当于跟着像素“走”完整条轨道def fit_rails(binary): h, w binary.shape histogram np.sum(binary[h // 2:, :], axis0) mid w // 2 left_base np.argmax(histogram[:mid]) right_base np.argmax(histogram[mid:]) mid nonzero binary.nonzero() y_pts, x_pts nonzero[0].astype(int), nonzero[1].astype(int) nwindows, margin, minpix 9, 100, 50 win_h h // nwindows left_x, right_x left_base, right_base left_inds, right_inds [], [] for win in range(nwindows): y_low h - (win 1) * win_h y_high h - win * win_h cond_l (y_pts y_low) (y_pts y_high) (x_pts left_x - margin) (x_pts left_x margin) cond_r (y_pts y_low) (y_pts y_high) (x_pts right_x - margin) (x_pts right_x margin) left_inds.append(np.where(cond_l)[0]) right_inds.append(np.where(cond_r)[0]) if np.count_nonzero(cond_l) minpix: left_x int(np.mean(x_pts[cond_l])) if np.count_nonzero(cond_r) minpix: right_x int(np.mean(x_pts[cond_r])) left_inds np.concatenate(left_inds) right_inds np.concatenate(right_inds) left_fit np.polyfit(y_pts[left_inds], x_pts[left_inds], 2) right_fit np.polyfit(y_pts[right_inds], x_pts[right_inds], 2) return left_fit, right_fit这个函数的输入是前面处理好的二值图输出是两条轨道的二次多项式系数。拿到系数之后可以沿着图像高度逐行计算轨道中心的x坐标用来画线、算轨距非常方便。注意实际用的时候要给左右空窗口加保护左轨道在画面外时直方图可能只有本文还有配套的精品资源点击获取