ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于Python和OpenCV的电梯开关门视频门位置识别与门缝宽度检测

基于Python和OpenCV的电梯开关门视频门位置识别与门缝宽度检测 简介面向计算机视觉与视频分析开发者的实践型资源针对电梯开关门监控场景解决多部电梯门在每一帧中的位置识别问题。方法不依赖机器学习而是通过经典OpenCV图像处理流程将视频帧转为灰度、高斯模糊去噪、Canny边缘检测、查找轮廓并按面积过滤再以轮廓x坐标排序取两个候选门框逐帧绘制绿色边界框完成可视化。资源为单个docx文档共1个文件压缩包大小约15KB正文包含完整可直接运行的Python代码、detect_doors与process_video两个核心函数的逐段解释、轮廓排序与过滤逻辑的注意事项以及参数调整建议和适用边界说明如门与环境颜色纹理差异不足时需引入更复杂的目标检测方法。已有107人学习下载。对于具备Python与OpenCV基础、希望快速理解并复现边缘检测和轮廓分析在真实视频场景应用的读者可作为简明的代码模板与排错参照。1. 电梯开关门视频门位置识别到底在解决什么问题电梯厂里的门系统测试、维保单位的年度检查经常要对着监控视频核一件事门有没有完全关闭到位、开关门动作是否平顺、开门宽度是否达标。过去靠人一帧帧盯一小时视频看得眼睛发花还容易漏掉关键帧。基于 Python 和 OpenCV 的电梯开关门视频门位置识别就是把这个人工核对的过程自动化从视频每一帧里找出左右门板边缘对应的像素坐标算出当前门缝宽度和开门进度再落成 CSV 与曲线供质量分析。这类任务天然适合固定机位、门板运动轨迹可控的电梯门监控场景用传统图像处理手段就能解决并不需要上深度学习。2. 先定识别目标门缝宽度、门板边缘还是开关门状态标题里写着“门位置识别”可真到写代码的时候先要回答的是另一个问题你说的“位置”到底指什么。这一步没想清楚后面的参数和结果全是空中楼阁。2.1 三种“门位置”定义决定你输出什么数据电梯门按结构大致分两类中分双开门和旁开单扇门。中分门的两扇门板从中间向两侧打开旁开门则是一块门板整体平移。绝大多数办公和住宅电梯都是中分双开门所以下文以它为主线旁开门只需要把算法改成追踪单侧边缘。门位置识别可以拆成三种可输出的定义。第一种是门缝宽度也就是左右两扇门板内侧边缘之间的像素距离。这个量最直观关门到位时它是一个接近零的小值开门过程中逐步增大到接近门洞宽度。第二种是单侧门板边缘的绝对像素坐标它适合旁开门也适合对左右门板运动不对称的场景做精细分析。第三种是开关门状态本质上是把第一种或第二种量做阈值化门缝宽度小于一定值判为关闭大于一定值判为开启中间过程算开闭中。三种定义落到代码里就是三种完全不同的输出形式选错会直接导致后期分析无从下手。我一般在实际动手前会先打开视频截取关门到位、半开、全开三帧看看门板边缘在图像里到底是清晰的边线还是模糊的反光带再决定用哪种输出。表三种输出定义对比输出定义计算方式适用场景实现难度门缝宽度左门板内缘到右门板内缘的像素差中分门关门到位检测、开门行程统计低单侧门板边缘坐标追踪一条门板边缘的 x 坐标旁开门、单门板运动分析中开关门状态门缝宽度阈值化或边缘位置判定状态记录、故障日志低一旦确定输出定义后续的 ROI 框选、边缘检测、参数调整都有了明确靶子。比如你想测门缝宽度ROI 就应该框住两扇门板的中缝区域你想测单侧门板边缘ROI 就要放在这一侧门板的运动路径上。很多人一上来就对着整个画面跑 Canny结果把门框、轿厢壁、楼层按钮全部卷了进来识别结果里全是无关边缘这就是目标没定义清楚的后遗症。2.2 摄像头机位和坐标换算像素不是物理尺寸视频里的门位置是一串像素坐标但业务上要的是物理概念门关没关严、开了多少毫米。所以第二个要解决的问题是像素和物理长度的换算。固定机位下如果摄像头光轴近似垂直于门板平面像素到毫米的换算非常简单测出画面里某一已知物体的像素宽度再用物理宽度除以像素宽度得到比例系数。常见的参照物是标准门洞宽度国标里中分门净开门宽度常见的规格是 900 毫米或 1200 毫米只要在画面里量出门洞总宽对应的像素数比例就出来了。但现实里的监控摄像头很少正对门板大多有一定的俯角和偏角。俯角会造成近大远小门板上下边缘在画面里不平行偏角会让原本垂直的门缝变成斜线。这时候强行用一个固定比例换算开门到不同位置时误差会明显变化。常见的做法是先做透视矫正用 cv2.getPerspectiveTransform 把画面里门板的四个角点映射成一个矩形再在这个校正后的图面上做检测。代价是多一次标定操作还要注意校正后画面边缘会产生拉伸ROI 框选必须在校正后的图像上重新做。如果只是做门有没有关到位这种定性判断不做矫正也能用但要输出“开门宽度毫米数”这种定量指标透视矫正不能省。另外镜头畸变在画面边缘最明显ROI 尽量放在画面中央区域能减少不少麻烦。2.3 为什么用 OpenCV 而非深度学习成本和可控性门位置识别为什么不用目标检测或语义分割模型最直接的原因是样本和投入不匹配。电梯门视频场景高度单一一段视频里只有一台电梯、一扇门、固定的背景。深度模型要落地需要收集大量不同光照、不同角度、不同门型的数据并标注而且换一个项目又要重新采集。传统视觉方法里Canny 边缘检测、形态学处理、列投影这些手段在固定场景下已经足够稳定调试时也直观参数不对马上能在画面上看到原因。部署环境也是个现实约束。深度模型推理至少要能跑 GPU哪怕用轻量模型在 CPU 上也难以保证长时间视频的实时处理。OpenCV 方案在普通工控机甚至树莓派上都能跑内存占用小依赖只有 opencv-python 和 numpy这对电梯维保这种需要在现场快速搭建的场合非常友好。可解释性更是传统方法的一大优势。识别结果可以直接把边缘位置画在原始帧上维保人员一眼就能看出算法盯住的是不是真正的门板边缘。深度模型再准出现误判时也很难说清楚是哪个环节出了问题。当然传统方法的代价也摆在明面光照突变、反光、遮挡都会让它翻车所以后面几章的重点全在参数调优和异常处理上。3. 用 OpenCV 搭建最小识别管线读帧、 ROI 、列投影完整代码目标定清楚后就可以开始写代码了。这一章给出一套最小可运行的识别管线从打开视频到输出 CSV每段代码都能直接复制运行。3.1 环境准备Python 和 OpenCV 安装常见报错一次解决项目只需要两个核心依赖pip install opencv-python numpy安装 OpenCV 最常见的一个坑是 pip 里包名不是 opencv而是 opencv-python。有人惯性写pip install opencv装出来的是另一个名字的库import cv2 时直接报 ModuleNotFoundError: No module named cv2。遇到这个报错先用 pip list 确认安装的是 opencv-python 而不是其它名字。另一个常见报错是 cv2.error: OpenCV 4.x 里某些函数调用方式变了。比如 cv2.findContours 在老版本返回 3 个值新版本返回 2 个值照抄网上老代码很容易踩。建议把依赖写成 requirements.txt锁定一个版本区间避免换一台机器后行为不一致。# requirements.txt opencv-python4.5 numpy1.21代码开头先打印版本号做确认import cv2 import numpy as np print(OpenCV version:, cv2.__version__) print(NumPy version:, np.__version__)逻辑说明这一步不是仪式而是排查环境问题的第一道防线。OpenCV 4.x 和 3.x 的 API 有不少差异如果运行时报参数个数错误先看版本是不是和代码预期的一致。NumPy 版本过低也会在 OpenCV 的数组运算里出现奇怪报错所以两者一起确认。3.2 视频读取与 ROI 框选先告诉程序“门在哪”视频读取用 cv2.VideoCapture需要注意它构造函数只做打开动作真正的错误检查要通过 isOpened 完成。cap cv2.VideoCapture(elevator_door.mp4) if not cap.isOpened(): raise IOError(无法打开视频请检查文件路径或编码格式) fps cap.get(cv2.CAP_PROP_FPS) total_frames int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) print(ffps{fps}, total_frames{total_frames})逻辑说明fps 和 total_frames 后面要用到前者把帧序号换算成时间后者用来预判视频长度。有些视频文件元数据损坏get 返回 0这时需要在读帧循环里自己计数并记录时间戳。门的位置在画面里是固定的所以应该框出一个矩形 ROI 只做检测。推荐的做法是取第一帧用 cv2.selectROI 手动交互框选ok, ref_frame cap.read() if not ok: raise IOError(第一帧读取失败视频文件可能已损坏) # 弹窗里用鼠标框住门板门缝区域回车确认ESC 取消 roi cv2.selectROI(select door region, ref_frame, showCrosshairTrue) cv2.destroyWindow(select door region) x, y, w, h roi print(fROI x:{x}, y:{y}, w:{w}, h:{h})逻辑说明selectROI 返回的四个值是矩形左上角坐标和宽高。框选的原则是把两扇门板都包含进去但不要把门框、井道背景、楼层按钮包进来。框小了门全开时门板跑出 ROI 导致边缘消失框大了检测结果里混入大量无关边缘。ROI 位置一定要留到后面处理循环里统一使用不要每帧重新算。提示selectROI 的坐标是相对整个原始帧的后续逐帧处理时 ROI 坐标要叠加在原始图像坐标系上不要弄混。3.3 核心识别函数Canny 边缘检测 形态学膨胀 列投影门板在画面里是两条近乎垂直的边缘线门缝则是两线之间相对暗的区域。识别门位置本质是找出这两条垂直边缘各在哪一列。下面是核心函数def detect_door_edges(gray_roi, low_threshold50, high_threshold150, dilate_kernel_size3, min_gap_px10): 输入灰度 ROI输出左右门板边缘在 ROI 内的列坐标。 返回 (left_x, right_x)检测失败返回 None。 # 高斯模糊去掉传感器噪点核不能太大否则门缝边缘也被磨平 blurred cv2.GaussianBlur(gray_roi, (5, 5), 0) # Canny 边缘检测低阈值控制连通性高阈值控制强边缘保留 edges cv2.Canny(blurred, low_threshold, high_threshold) # 膨胀一步让断断续续的垂直边缘连成实线 kernel cv2.getStructuringElement( cv2.MORPH_RECT, (dilate_kernel_size, dilate_kernel_size)) edges cv2.dilate(edges, kernel, iterations1) # 逐列统计边缘像素数量垂直边缘会在对应列形成峰值 col_sum np.sum(edges, axis0) / 255.0 if col_sum.max() 5: return None # 整帧都没有明显垂直边缘 # 取最大峰值的 60% 作为判定线低于它的都算噪声 threshold 0.6 * col_sum.max() cols np.where(col_sum threshold)[0] # 相邻列合并成一条边缘避免把同一条边拆成多条 groups [] for c in cols: if groups and c - groups[-1][-1] 3: groups[-1].append(c) else: groups.append([c]) edge_positions [int(np.mean(g)) for g in groups] # 在候选边缘里找一对相距不小于 min_gap_px 的且总强度最大 best_pair None best_score 0.0 for i in range(len(edge_positions)): for j in range(i 1, len(edge_positions)): gap edge_positions[j] - edge_positions[i] if gap min_gap_px: continue score col_sum[edge_positions[i]] col_sum[edge_positions[j]] if score best_score: best_score score best_pair (edge_positions[i], edge_positions[j]) return best_pair逻辑说明整段代码的关键是“列投影”。门板边缘在图像里是垂直走向投影后这一列会积累大量边缘像素点形成局部峰值。对边缘图做逐列求和后门板边缘就是峰值列。合并相邻列是为了应对膨胀后边缘变粗的情况不合并会出现同一条边产生两个坐标的假象。参数说明low_threshold 和 high_threshold 是 Canny 的双阈值low 决定边缘连通的敏感度high 决定哪些强边缘被保留。dilate_kernel_size 是膨胀核边长3×3 是常用起点调大可以让断边更快接上但也有副作用见第 4 章。min_gap_px 是门缝宽度的下限用来过滤掉门板表面拉丝纹理产生的相邻伪边缘。3.4 全视频循环逐帧检测并导出 CSV核心函数就位后整段视频的循环处理非常简单results [] frame_idx 0 cap.set(cv2.CAP_PROP_POS_FRAMES, 0) while True: ok, frame cap.read() if not ok: break # 整帧转灰度再裁剪 ROI减少计算量 gray_full cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) roi_gray gray_full[y:y h, x:x w] pair detect_door_edges(roi_gray) if pair is None: results.append((frame_idx, None, None, None)) else: lx, rx pair # ROI 内坐标换算回整帧坐标 results.append((frame_idx, lx x, rx x, rx - lx)) frame_idx 1 # 导出 CSV帧序号、时间、左右门板边缘坐标、门缝宽度 import csv with open(door_gap.csv, w, newline) as f: writer csv.writer(f) writer.writerow([frame, time_s, left_x, right_x, gap_px]) for i, l, r, g in results: writer.writerow([i, round(i / fps, 3), l, r, g]) print(f处理完成共 {frame_idx} 帧结果已写入 door_gap.csv)逻辑说明每一帧都重新跑完整的边缘检测不做帧间缓存这是为了保证代码简单、可复现。代价是每一帧的检测结果相互独立偶尔会出现一帧跳变后处理时再做平滑解决。CSV 里同时保存了 ROI 内坐标和整帧坐标调试时用整帧坐标可以直接画线回看。参数说明l 和 r 加上了 ROI 的偏移量 x这样输出坐标和原画面对齐。gap 是 rx 减 lx也就是门缝的像素宽度。由于视频里的门缝宽度会随像素分辨率变化这个值只有在标定后才有物理意义标定方法见第 6 章。4. 必须调对的核心参数Canny 阈值、膨胀核与 ROI 范围代码能跑只是第一步真正决定识别稳定性的是一组参数调优。Canny 阈值、膨胀核、ROI 范围这三个是电梯门场景里最容易出问题的环节下面逐个拆开讲。4.1 Canny 高低阈值在不同光照下怎么定Canny 的两个阈值low_threshold 和 high_threshold控制的是“什么样的边缘算数”。高阈值决定了只有梯度够大的像素才被判为强边缘低阈值则决定边缘的连通程度。两者不是独立关系一般经验是高阈值取低阈值的 2 到 3 倍。电梯门井道一侧常有窗户或照明灯白天阳光直射时门板表面亮度高高阈值如果设太低门板上的阳光反光边缘也会被保留识别结果会被反光带带跑。夜间或地下车库光线弱低阈值设太高门板边缘和背景对比度不够Canny 就检不出完整的门板线。常见的做法是先做直方图均衡化或 CLAHE 预处理把光照波动压下去再做边缘检测def preprocess_roi(gray_roi, clip_limit2.0, tile_size8): CLAHE 自适应直方图均衡抑制光照不均的影响。 clahe cv2.createCLAHE(clipLimitclip_limit, tileGridSize(tile_size, tile_size)) return clahe.apply(gray_roi)逻辑说明CLAHE 在图像局部块内做直方图均衡把原本过亮或过暗区域的细节都拉伸到可见范围。它不会像全局直方图均衡那样把大面积反光的对比度过度放大更适合电梯门这种有渐变光照的场景。参数说明clip_limit 是对比度限制值太大反而会放大噪声2.0 左右是稳妥起点。tile_size 是分块尺寸8 是默认值ROI 较小时可改小到 4。用了 CLAHE 之后Canny 的低阈值可以适度降低到 40 左右因为边缘信息已经被增强了。调参顺序我建议这样做先打开一帧关门图把 Canny 阈值调到能完整看到门板两条竖边、且门板表面纹理不产生额外边缘为止再打开一帧开门到一半的图确认门板边缘没有断裂最后用整段视频里光照最极端的一帧验证。概率上说这个顺序能覆盖 80% 的翻车情况。4.2 膨胀核与迭代次数边缘断裂和边缘偏移怎么平衡膨胀操作用于把 Canny 输出的断续边缘连成连续线段。电梯门板边缘在画面上通常有几十到几百像素长但边缘检测经常会因为光照或门板反光出现小段缺失。膨胀核设对了断口能很快接上。膨胀核不是越大越好。这就像用粗画笔描边描得越粗边缘位置就越偏离真实门板位置。3×3 的矩形核对大多数 960 宽度的画面已经够用。只有当 ROI 分辨率很高、门板边缘细碎断裂特别多才考虑 5×5。迭代次数也是这样iterations1 是默认推荐增加到 2 会明显让边缘变粗 1 到 2 个像素直接导致门缝宽度测量多出几个像素的偏差。如果发现边缘总在固定位置断裂不要急着加膨胀迭代。先回去调 Canny 的低阈值往往更有效。膨胀把门板纹理也一起连进来时会产生一条假边缘并把列投影的峰值带偏这是膨胀过度的典型表现。表膨胀参数调整场景现象推荐调整原因门板边缘断续、投影无峰值膨胀核从 3×3 改 5×5 或迭代加 1让边缘断口接上识别坐标整体偏移 1~2 像素核调回 3×3迭代减回 1膨胀让边缘位置偏移门板纹理被当成边缘调低 Canny 低阈值不动膨胀膨胀会把纹理连成假边4.3 ROI 范围框得准不准决定了一半识别率ROI 是这套方案里最值得花时间精调的参数没有之一。ROI 框的是“门板运动经过的区域”不是“画面里看起来亮的区域”。框得太大门框、楼层按钮、轿厢壁反光、井道里的导轨全部进入检测范围。列投影会在这些物体的垂直边缘处产生一堆峰值门板边缘反而不是最强的算法就会盯错目标。框得太小问题出在另一头门全开时门板跑出 ROI边缘消失得到 None 或者只看到一条缝的假象。判断 ROI 是否合适的标准很简单把视频连续播放一遍在画面中画出 ROI 矩形观察整个开关门过程中门板是否始终落在矩形内且矩形边缘没有经常被门框或井道背景侵占。实际操作里我会留一个余量把关门状态门缝中线左右各留出门洞宽度一半的像素这样门全开时门板内侧边缘仍在 ROI 内。同时上下方向只框住门板的中段避开顶部门楣和底部地坎这两处在画面里常常有强边缘干扰。ROI 的参数最好集中放在一个配置字典里方便后续换摄像头时只改配置detect_config { roi: {x: 200, y: 120, w: 800, h: 300}, canny: {low: 50, high: 150}, dilate_kernel: 3, min_gap_px: 10, }4.4 备选方法霍夫直线检测和帧差法的适用边界列投影方案不是唯一选择另外两种常见方案各有适用边界。霍夫直线检测可以处理门板边缘在画面里带倾斜角的情况。cv2.HoughLinesP 输出的是线段端点可以通过角度筛掉非垂直的边缘看起来比列投影更灵活。但它的参数更多minLineLength、maxLineGap、累加器阈值都要配调起来比列投影费时间而且它对线段间断的敏感度高门板反光导致边缘中间断了霍夫会把一条边拆成两段后期还要做线段合并。帧差法也就是相邻帧差分适合检测正在运动的物体边缘。电梯门开关过程中门板在动用帧差能很好地把运动的门板和静止的背景分离。但门完全静止时帧差结果为零无法判断关门到位的状态所以它只能作为辅助手段不能单独承担门位置识别。我的习惯是把列投影作为主算法因为它在垂直门板上最简单参数也少调试成本最低。只有在摄像头角度刁钻、门板边缘明显倾斜的情况下才切换到霍夫直线并增加角度过滤。5. 电梯门识别常见问题排查五条翻车记录与解法固定场景的传统视觉方法翻车点其实非常集中。下面五条是电梯门场景里最高频的问题每条都是从实际调试中总结出来的现象、原因和解决路径。5.1 现象门关到位却报“门缝还有 20 像素”一段关门视频画面里两扇门已经严丝合缝算法却一直输出 gap 约 20 像素直到门重新打开才恢复正常。原因ROI 框选范围包含了门板外侧的井道背景或者门板边缘和门框边缘同时被检测出来。算法找“距离大于 min_gap_px 的强边缘对”时把门框边缘和门板边缘配成了一对于是门关了但它看到的还是两个相隔 20 像素的边缘。解决先检查 ROI 是否把门框、井道背景包含进来了收紧 ROI 是最快的办法。再给配对逻辑加一个物理上限约束比如门洞宽度的像素数超过这个值就不可能是门缝。最稳妥的办法是记录一段关门状态作为基准线后续所有 gap 判定都以基线为准# 在视频前几帧门完全关闭时记录基线 baseline_gap 10 # 示例值实际取关门状态连续 10 帧的中位数 is_closed gap baseline_gap 5逻辑说明基线法把“绝对门缝宽度”的判定改成了“相对于正常关门状态的偏差”相当于做了一次零点校准。它比绝对阈值稳定得多因为不同项目里门缝在画面里的像素宽度不同绝对阈值换一个场景就要重新定。5.2 现象开门瞬间坐标跳变 30 像素随后又恢复正常处理结果曲线里门开始打开的时刻 x 坐标突然跳了 30 像素然后下一帧又回到正常轨迹形成一个明显的尖峰。原因开门瞬间门板运动速度最快画面里产生运动模糊边缘检测结果在模糊帧上不稳定。列投影可能在这一帧里把门板上的某个反光细节当成了边缘导致坐标突变。解决给输出加时间维度的平滑。常见做法是中值滤波它比均值滤波更能保留边缘突变又能压掉尖峰import numpy as np def median_smooth_series(values, window5): values np.array(values, dtypefloat) out values.copy() half window // 2 for i in range(len(values)): seg values[max(0, i - half): i half 1] valid seg[~np.isnan(seg)] if len(valid) 0: out[i] np.nan else: out[i] np.median(valid) return out逻辑说明这个函数把每一帧的值替换成前后各两帧的中位数。运动模糊造成的尖峰通常只有一两帧中值滤波能有效剔除。窗口大小 5 是起点如果识别帧率很高可以增到 7 或 9但窗口太大会把真实的开门瞬间钝化让“开门速度”计算失真。5.3 现象不锈钢面板反光识别结果粘在装饰棱线上电梯门板是不锈钢材质表面有拉丝纹理和竖直的装饰棱线。识别结果长时间停在这些棱线上门缝位置反而被忽略。原因拉丝纹理在固定光源下会产生稳定的高对比度垂直线列投影的峰值强度比真实门板边缘还高算法被“更明显的假边缘”带走了。解决第一步把 ROI 收窄让装饰棱线不要进入检测区这是成本最低的解法。如果棱线在门板表面且在 ROI 里避不开就在边缘配对时增加一条约束真实门板边缘应该是 ROI 里最靠近中间的一对强边缘而装饰棱线通常成对出现在门板中段内侧。更通用的做法是引入边缘的位置先验比如把门缝中线附近的区域权重加大让离门缝预期位置更近的边缘获得更高分。def score_edges(edge_positions, col_sum, expected_center, roi_w): scores [] for e in edge_positions: # 离预期门缝中线越近得分越高衰减系数 0.02 可按需调 position_prior max(0.0, 1.0 - abs(e - expected_center) * 0.02 / roi_w) scores.append(col_sum[e] * (0.5 0.5 * position_prior)) return scores逻辑说明给边缘候选加位置先验本质上是告诉算法“门缝大体在哪一带”。这样做在反光场景下特别有效反光棱线即使亮度高也会因为偏离预期位置而降低总分。expected_center 可以通过关门基准帧的门缝中线标定一次后续就固定不变。5.4 现象换一台摄像头同一套代码识别率骤降现场从 A 摄像头换成 B 摄像头分辨率、视角、色彩都变了原先调好的参数肉眼可见地失效误检率大幅上升。原因传统视觉参数本身就是和图像分辨率及视角绑定的。换摄像头后 ROI 坐标完全错位Canny 阈值在新的光照特性下不再匹配列投影的峰值分布也变了。解决把顶层设计改成“配置驱动”所有项目相关的参数都从配置文件读取代码本身不写死任何数字import json def load_config(pathdoor_config.json): with open(path, r, encodingutf-8) as f: return json.load(f) # 换摄像头后只改这个文件 config { video_path: elevator_B.mp4, roi: {x: 180, y: 100, w: 700, h: 260}, canny: {low: 40, high: 120}, dilate_kernel: 3, min_gap_px: 8, baseline_gap: 12 }再配合一个统一的预处理缩放逻辑无论输入视频分辨率是多少先缩放到一个固定的处理宽度比如 960 像素。这样换摄像头后只需要重新标定 ROI 和阈值代码不用动。def prepare_frame(frame, target_width960): h, w frame.shape[:2] scale target_width / w if scale ! 1.0: frame cv2.resize(frame, (target_width, int(h * scale))) return frame, scale逻辑说明scale 返回给调用方后续所有像素坐标除以 scale 即可还原到原始分辨率。缩放的目的是让不同分辨率视频走同一套参数分布减少重复调参工作。要注意的是缩放会损失细节如果原始分辨率很低且门板很小缩放反而有害这时应该针对该场景单独标定。5.5 现象有人在门缝前站立识别曲线乱跳视频里乘客站在门缝前等待身体部分挡住门板列投影的峰值瞬间被衣服的竖向褶皱或人体轮廓干扰识别结果毫无规律。原因人体遮挡改变了 ROI 内的边缘分布衣服的边缘、轮廓线混进门板边缘里列投影选出的“最强边缘对”已经不是门板。解决遮挡场景下最直接的做法是检测到异常后输出“无效”而不是输出错误坐标。判断依据可以是边缘对的强度与历史均值的偏离程度或门缝宽度的变化率超过合理范围def is_occluded(gap, prev_gap, max_delta0.3): prev_gap前一帧的门缝宽度 max_delta单帧允许的最大相对变化 if prev_gap is None or prev_gap 0: return False return abs(gap - prev_gap) / prev_gap max_delta逻辑说明门板运动是连续且平滑的正常情况下相邻帧门缝宽度变化率不会超过 30%。人体遮挡会在几帧内让检测值剧烈变化超过这个阈值就判定为遮挡或异常帧数据标记为无效而不是参与后续统计。这个策略比起硬扛遮挡更适合生产环境因为电梯门系统分析最关心的是门本身的运动曲线遮挡段本来就是数据缺失。表本节五类问题速查现象首选解决备选解决关门误报宽缝收紧 ROI加基线判定限制最大门缝宽度开门瞬间坐标跳变中值滤波平滑加运动速度约束反光棱线干扰收窄 ROI 避开反光带边缘位置先验加权更换摄像头失效配置驱动统一缩放重新标定 ROI 和阈值人体遮挡乱跳变化率异常判定为无效遮挡段插值补全6. 让结果可信像素标定、时间平滑与真值验证识别曲线出来了离真正能用还差三步把像素换成物理单位把噪声压下去用真实数据验证误差。像素到毫米的标定按下述方式做在画面里量出门洞总宽对应的像素数用已知物理宽度除以它得到每像素对应的毫米数。# 假设画面里门洞总宽为 720 像素实际门洞净宽 900 毫米 pixel_per_mm 720 / 900 # 0.8 像素/毫米 gap_mm gap_px / pixel_per_mm逻辑说明这个换算只适用于门板平面和摄像头光轴接近垂直的情况。如果有明显俯角或偏角先做透视矫正再标定否则开门不同位置换算的误差会不一样。单位换算之后后续所有的统计指标都要用毫米值。平滑和验证我一般会在同一帧画面里画出识别边缘和实际门缝位置的叠加图肉眼扫一遍再下结论。这个方法虽然土但效果最直接。遇到一条识别曲线整体比真实门缝偏内或偏外几个像素说明边缘位置有系统偏差检查焦距或矫正参数遇到个别帧跳动中值滤波就能兜住。我习惯在新场景里先花二十分钟调 ROI 和 Canny 阈值再拿一小段视频跑通全流程最后才对整段视频批量处理。识别率低的时候九成问题都出在 ROI 框得不准或光照没处理而不是算法本身。把这几件事做扎实基于 Python 和 OpenCV 的门位置识别就能真正落到维保和故障分析里希望帮到你。本文还有配套的精品资源点击获取
返回列表