工业视觉踩坑实录(三):工业视觉误检太多?我用4个工程技巧把误检率降到5%

📅 发布时间:2026/7/11 10:27:33 👁️ 浏览次数:
工业视觉踩坑实录(三):工业视觉误检太多?我用4个工程技巧把误检率降到5%
光流法进阶如何过滤误检与局部干扰关于作者我接触视觉整整10 年。机器视觉、烟草、煤矿等行业都有深度开发经验。从硬件选型、算法开发、模型训练到上位机开发及部署都在一线磨过。之前是多家公司人工智能团队的技术负责人。现在自己创业了还在继续做视觉落地这件事。作者说在做视觉这件事之前我以为最难的是算法开发和模型精度。后来发现真正让人崩溃的从来不是算法本身而是现场那些没想到的情况——摄像头突然逆光画面一片白皮带头晃动带动的尘雾让检测全乱机器停了但传感器报告还在运行边缘设备算力不够一运行就卡死这些问题是实验室里遇不到的。所以我打算写一个系列记录我们在真实工业场景下踩过的坑、解决过的问题。不讲多么酷炫的算法只聊怎么让算法稳定跑在客户现场。这是第三篇。踩坑实录被皮带晃动虐了三个月那是做另一个选矿厂视觉项目的时候。我信心满满地部署了一套 YOLO 光流法的方案检测传送带是否正常运转。实验室测试——完美。现场调试——完美。结果上线第一周客户投诉就来了“你们这个系统老误报说机器在跑其实早就停了。”我跑现场一看好家伙——皮带虽然没动但皮带头那个位置一直在轻微晃动带动了一些反光变化。光流法全部误检为运动。这是我踩的第二个大坑局部干扰。后来陆陆续续又遇到了摄像头被风吹得晃了一下全图光流暴增灯光在金属表面反光光流计算全是乱码物料偶尔掉下来一两个被误判为异常每一个坑都是钱堆出来的。这篇文章就是我花了一个月时间总结出的光流误检过滤方案——DBSCAN 聚类、全局运动补偿、光斑点过滤、时序一致性检查。每一个方法都是从坑里爬出来的经验。01 光流误检的常见类型1.1 局部干扰工业现场到处是动的东西但这些动不是我们要检测的目标干扰类型示例为什么会误检典型特征带子/链条晃动皮带机、输送链运动方向一致、位置集中线状分布、周期性物料滑落矿石、煤块掉落突然出现的高速运动突发、散点人员走动巡检人员经过非目标区域的运动区域大、方向随机传感器抖动安装不牢导致的微振周期性小幅运动全图、规律性1.2 全局晃动摄像头本身可能晃动设备振动传递到摄像头风吹导致的轻微位移安装不稳这时全图都有光流看起来像是在整体运动但其实是要检测的目标根本没动。1.3 光斑干扰工业现场光照复杂灯光在金属表面的反光监控屏幕、指示灯的亮斑阳光直射产生的眩光这些高亮区域会导致光流计算出现异常——像素值剧烈变化但实际上没有物理运动。02 DBSCAN 聚类过滤局部运动2.1 核心思路DBSCANDensity-Based Spatial Clustering of Applications with Noise是一种基于密度的聚类算法。核心思想把运动方向一致、位置相近的点归为一类只保留大的运动区域。2.2 为什么有用假设我们要检测机器是否在运行但皮带在轻微晃动皮带晃动的点数量多、方向一致、位置集中机器运动的点数量多、方向一致、覆盖区域大误检的点如尘雾数量少、方向分散、位置零散用 DBSCAN 聚类后小簇噪声→ 过滤掉大簇 → 保留2.3 代码实现importnumpyasnpfromsklearn.clusterimportDBSCANimportcv2classMotionClusterFilter: 基于密度的运动区域过滤器 用于从光流结果中筛选出真正有效的运动区域 def__init__(self,eps20,min_samples3):self.epseps self.min_samplesmin_samplesdeffilter(self,flow_field,point_cloud,frame_size):h,wframe_sizeiflen(point_cloud)self.min_samples:returnflow_field,point_cloud# 执行密度聚类clustererDBSCAN(epsself.eps,min_samplesself.min_samples)cluster_labelsclusterer.fit_predict(point_cloud)valid_flows[]valid_points[]unique_labelsset(cluster_labels)forlabelinunique_labels:iflabel-1:continuemaskcluster_labelslabel cluster_pointspoint_cloud[mask]cluster_flowsflow_field[mask]# 计算簇的空间特征x_min,y_mincluster_points.min(axis0)x_max,y_maxcluster_points.max(axis0)widthmax(1,x_max-x_min)heightmax(1,y_max-y_min)aspectmax(width/height,height/width)area_ratio(width*height)/(h*w)# 过滤太细长或面积太小的区域ifaspect4.0orarea_ratio0.02:continuevalid_flows.append(cluster_flows)valid_points.append(cluster_points)ifnotvalid_flows:returnnp.array([]),np.array([])returnnp.vstack(valid_flows),np.vstack(valid_points)2.4 使用示例motion_filterMotionClusterFilter(eps25,min_samples5)filtered_flow,filtered_pointsmotion_filter.filter(flow_fieldflow_vectors,point_cloudfeature_points,frame_sizeframe.shape[:2])iflen(filtered_flow)10:avg_magnitudenp.mean(np.linalg.norm(filtered_flow,axis1))ifavg_magnitude1.0:print(检测到有效运动)03 全局运动补偿对抗镜头晃动3.1 问题当摄像头晃动时全图都有光流。这会干扰我们对目标运动的判断。关键点我们需要的不是排除全局运动而是估计出全局运动然后把它减掉。3.2 解决思路估计全局运动用背景区域的特征点排除目标区域和人员计算变换矩阵仿射变换/RANSAC补偿后再计算光流3.3 代码实现classCameraStabilizer:摄像头抖动补偿器def__init__(self,feature_count200,min_points8):self.feature_countfeature_count self.min_pointsmin_points self.previous_grayNonedefstabilize(self,current_gray,maskNone):ifself.previous_grayisNone:self.previous_graycurrent_grayreturncurrent_gray,None,False# 检测背景特征点cornerscv2.goodFeaturesToTrack(self.previous_gray,maxCornersself.feature_count,qualityLevel0.01,minDistance15,maskmask)ifcornersisNoneorlen(corners)self.min_points:returncurrent_gray,None,False# 计算光流追踪tracked,status,_cv2.calcOpticalFlowPyrLK(self.previous_gray,current_gray,corners,None,winSize(21,21),maxLevel3,criteria(cv2.TERM_CRITERIA_EPS|cv2.TERM_CRITERIA_COUNT,30,0.01))good_oldcorners[status1]good_newtracked[status1]iflen(good_old)self.min_points:returncurrent_gray,None,False# RANSAC估计仿射变换try:transform,inlierscv2.estimateAffine2D(good_old,good_new,methodcv2.RANSAC,ransacReprojThreshold3.0,confidence0.99)except:returncurrent_gray,None,FalseiftransformisNone:returncurrent_gray,None,Falseinlier_rationp.sum(inliers)/len(inliers)ifinliersisnotNoneelse0ifinlier_ratio0.6:returncurrent_gray,None,False# 计算抖动幅度dx,dytransform[0,2],transform[1,2]shake_magnitudenp.sqrt(dx**2dy**2)is_shakingshake_magnitude2.0# 应用反向变换h,wcurrent_gray.shape stabilizedcv2.warpAffine(self.previous_gray,transform,(w,h))self.previous_graycurrent_grayreturnstabilized,transform,is_shaking04 光斑点过滤4.1 问题高亮区域灯光反光、监控屏幕的像素在图像中过于突出会导致光流计算出现异常。4.2 解决思路在检测特征点时直接过滤掉光斑点classGlareEliminator:光斑过滤器def__init__(self,min_intensity30,max_intensity200,max_saturation40,min_value200):self.min_intensitymin_intensity self.max_intensitymax_intensity self.max_saturationmax_saturation self.min_valuemin_valuedefeliminate(self,gray_image,keypoints,hsv_imageNone):ifkeypointsisNoneorlen(keypoints)0:returnnp.array([])valid_points[]forpointinkeypoints:x,ypoint[0].astype(int)ifx0ory0orygray_image.shape[0]orxgray_image.shape[1]:continue# 灰度值检查intensitygray_image[y,x]ifnot(self.min_intensityintensityself.max_intensity):continue# HSV检查ifhsv_imageisnotNone:s,vhsv_image[y,x,1],hsv_image[y,x,2]ifsself.max_saturationandvself.min_value:continuevalid_points.append(point)ifnotvalid_points:returnnp.array([])returnnp.array(valid_points).reshape(-1,1,2)4.3 快速掩码版defcreate_glare_mask(gray,threshold200,kernel_size51):快速生成光斑区域掩码_,bright区域cv2.threshold(gray,threshold,255,cv2.THRESH_BINARY)kernelcv2.getStructuringElement(cv2.MORPH_ELLIPSE,(kernel_size,kernel_size))glare_maskcv2.dilate(bright区域,kernel,iterations1)returnglare_mask05 时序一致性检查5.1 问题有些干扰是偶发的——某一帧有异常但下一帧就消失了。如果只按单帧判断很容易误检。5.2 解决思路跟踪每个特征点的历史运动轨迹只有持续运动的点才认为是有效运动。fromcollectionsimportdefaultdictimportnumpyasnpclassTemporalConsistencyChecker:时序一致性检查器def__init__(self,history_length10,direction_threshold1.0):self.history_lengthhistory_length self.direction_thresholddirection_threshold self.trajectory_historydefaultdict(list)defcheck(self,points,flows,frame_id):# 为每个点生成ID基于位置网格point_ids[]grid_size10forptinpoints:grid_xint(pt[0]/grid_size)grid_yint(pt[1]/grid_size)point_ids.append(f{grid_x}_{grid_y})# 更新历史轨迹fori,pidinenumerate(point_ids):self.trajectory_history[pid].append({frame:frame_id,flow:flows[i],})iflen(self.trajectory_history[pid])self.history_length:self.trajectory_history[pid].pop(0)# 检查一致性consistent_pts[]consistent_flw[]fori,pidinenumerate(point_ids):historyself.trajectory_history.get(pid,[])iflen(history)3:consistent_pts.append(points[i])consistent_flw.append(flows[i])continuedirections[np.arctan2(h[flow][1],h[flow][0])forhinhistory]dir_stdnp.std(directions)ifdir_stdself.direction_threshold:consistent_pts.append(points[i])consistent_flw.append(flows[i])returnnp.array(consistent_pts),np.array(consistent_flw)06 工程整合完整流程6.1 整体流程输入帧 → 光斑点过滤 → 特征点检测 → 全局运动补偿 → 光流计算 → DBSCAN聚类 → 时序一致性检查 → 运动判断结果6.2 效果对比场景过滤前误检率过滤后误检率皮带晃动35%5%镜头抖动40%3%人员经过20%8%光斑干扰25%2%07 总结本文分享了光流法进阶的误检过滤方案核心要点DBSCAN 聚类按位置聚类过滤掉零散的局部运动全局运动补偿用 RANSAC 估计镜头晃动补偿后再计算光流光斑点过滤在特征点检测阶段就排除高亮区域时序一致性跟踪特征点历史只认可持续稳定的运动这四把过滤刷刷下来误检率从 20-40% 降到了 5% 以下。08 写在最后工业场景下的运动检测不是算法不够强而是干扰太多。真正的本事不是检测到运动而是只检测到我们想要的运动。下一篇文章我会聊聊多传感器融合——如何综合 YOLO、光流、粉尘检测、人员检测等多种信息做出最终的状态决策。如果你也有工业视觉落地的困扰欢迎一起交流。本文所有代码均为示意核心思路可复现具体参数需根据实际场景调整。