前阵子整理自己的项目归档,翻到一行只有七个字的命名:提取光学南北-东西向。没有正文,没有关键词,没有配图,看起来像是一个随手记下来的备忘。但我盯着这行字看了很久,因为这类极简命名在工程里其实很典型——它压缩了一个项目最关键、也最容易让人忽略的信息:用光学手段去获得目标相对“南北”和“东西”的姿态、角度或线划量。这个需求横跨工业视觉、测绘仪器、光伏朝向检测、结构位移监测等好几个领域,光靠这个标题无法立刻确定是哪条具体路线,但我可以把它展开成一套完整、能直接复现的光学方向提取方法论。
我很久之前做过一个类似的方向测量设备:目标工件在传送带上随机摆放,系统需要通过光学成像实时判断它相对场地南北轴和东西轴的偏转角,然后把结果送给机械手做抓取定位。标题里的“南北-东西向”到了现场就是两条互相垂直的主轴线。这里面的核心问题永远是:先定义清楚谁是南北,谁是东西,再谈怎么用相机把方向算出来。整套流程下来,涉及的不只是图像处理算法,还包括光学照明、坐标系标定、正交性修正和现场抗干扰,任何一个环节掉链子,最终输出的角度看着有模有样,实际对不上真值。
这篇文章我就按实际项目的推进顺序来写,把从需求解析、硬件选型、坐标映射、算法主干到精度验证的所有环节都拆开说清楚,最后补上现场调试时最容易翻车的几个地方。
1. 需求只有七个字时:我为什么把它定义为光学朝向测量任务
1.1 一句话需求能对应多少种场景
“提取光学南北-东西向”这种简写,在现实中至少对应四种完全不同类型的项目。第一种是工业自动化里的工件方向识别,比如PCB板、光伏硅片、包装盒在产线上姿态任意,需要输出它们长轴相对场地南北方向的夹角,通常用于机械手引导或分拣;第二种是太阳能支架或天线安装前的基准测量,要用光学仪器测出支架主梁当前相对真南北、东西方向的角度偏差;第三种是地物遥感或航拍影像处理,在正射影像里提取建筑轮廓、道路或田埂的方向线,然后统计这些线段的走向分布;第四种则是健康监测里的二维光学位移测量,把结构物在水平面的位移拆成南北和东西两个分量,对应项目名里的“南北-东西向”就是两个观测方向。
这四种场景的物理载体很不相同,但底层数学框架是一致的:将光学传感器获取到的像素信息,换算到以正北和正东为基准的平面坐标系中,再解出目标的主方向角。所以在没有更多上下文的情况下,我会按“光学朝向测量任务”这个通用定义来搭建系统,读者在遇到具体需求时,只需要把“被测对象”替换成自己的工件或构筑物即可。
1.2 我这次沿用的技术路线
我先说清楚我在项目里固定采用的技术路线,后面所有内容都围绕它展开:用一个固定安装的工业面阵相机垂直俯视或斜视目标区域,配合主动光源把被测物的边缘特征稳定拍出来;然后通过标定把像素坐标变换到以场地北向为Y轴、东向为X轴的物理坐标系;再对图像中的直线边缘或矩形轮廓做方向拟合,输出0到180度之间的主方向角。
有时目标物本身有两条明显的正交边,比如矩形工件或矩形的设备基础。这时项目名称里的“南北-东西向”就对应两族直线:一条边缘族拟合出的角度就是南北向边和目标场地北方向的偏角,另一条边缘族的角度则反映东西向边缘的偏角。实际测量时不一定只输出两个独立角度,很多上位机还需要一个合成后的姿态角,用于评判产品放置是否合格,我当时把四个方向的公差范围单独做成配置项,比硬编码进算法里灵活得多。
1.3 验收标准和技术指标怎么定
做这种系统,第一件事不是写代码,而是和提出需求的人把指标敲定。通过“提取光学南北-东西向”这个项目名,只能推测出大方向,但必须追问:要测到多少精度?是绝对角度精度还是重复测量稳定性?现场目标最大尺寸多大?允许的工作距离多少?这些指标直接决定相机、镜头、光源和整个算法的复杂程度。
我习惯把一个检测站点的建议指标写成下面这样,方便各方确认:
| 指标项 | 建议目标值 | 说明 |
|---|---|---|
| 单次测量时间 | 小于200ms | 工业在线检测通常要求每个节拍内出结果 |
| 方向角重复性标准差 | 小于0.05度 | 同一工件放10次,输出角度波动范围很小 |
| 方向角绝对误差 | 小于0.3度 | 用高精度转台做真值比对 |
| 被测物最小边长度 | 不小于20像素 | 低于这个值边缘方向容易被像素噪声带偏 |
| 抗环境杂光能力 | 室内开灯/关灯不影响结果 | 主要靠主动光源和遮光结构 |
如果项目实际提出来的是地物方向提取,那么指标就换成位置精度、线段角度分组间隔等,但系统设计思路不变。总之,拿到极简需求时不要慌,先自己补出一版关于“基准、对象、精度、节拍”的理解,拿去和现场确认,比闷头写代码有效得多。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 硬件配置里的光学细节:镜头、光源和“谁是北”的基准
2.1 为什么这类测量离不开“光学”手段
项目名里特意强调“光学”,说明用户大概率已经排除了接触式测量。接触式测方向虽然能做得很准,但要靠探针沿两条边各扫一遍,速度慢且有可能损伤表面。光学测量的最大优势是“非接触、快、信息密度高”,一张两千万像素的图像里除了方向信息,还能顺便提取尺寸、缺陷、颜色分区等数据。很多视觉检测项目都要求一套硬件同时出多个结果,“光学”在产线里的地位很稳固,主要是因为它不需要碰被测物。
但光学测量也有一个绕不开的命门:它看到的是灰度或彩色分布,而不是物理坐标。目标边缘在图像里到底对应哪一行像素,受镜头畸变、光源角度、物体表面反射特性影响极大。方向测量对像素位置极其敏感,如果边缘因为光照不均整体向左偏移了半个像素,最终角度就可能偏出0.2度,这在项目验收时经常成为最大争议点。理解了这一点,硬件选型就不再是参数堆砌,而是要把“稳定成像”作为最高优先级。
2.2 相机、镜头、光源的选择逻辑
目标方向测量不涉及微观纹理,不需要超高分辨率,所以我通常选500万像素级别的黑白面阵工业相机。黑白相机在低照度下的信噪比更好,而且没有彩色插值带来的边缘模糊。像素数够用就行,宁可用一台500万像素但镜头光圈和畸变控制更好的相机,也不要盲目上1200万像素然后配一支普通CCTV镜头,因为镜头分辨率跟不上传感器时,边缘反而会被拖软。
镜头是最容易被低估的环节。普通FA镜头在视场边缘畸变可能达到1%以上,如果只测量目标中部的短边缘,影响尚可接受;但测方向经常要覆盖整个视场里多个不同位置的工件,边缘区域的直线会严重弯曲。我处理过最大的一台设备视场约500mm,用8mm焦距镜头时边缘畸变造成角度偏差超过0.8度,后期用畸变标定虽然能修回大部分,但白费了不少时间。从那以后,凡是要做0.1度级别方向测量的项目,稳定器我尽量选低畸变镜头或远心镜头,远心镜头在指定工作距离范围内几乎无畸变,边缘方向和实际方向的对应关系很干净,缺点只是贵、视场做不大。
光源方向比光源亮度更关键。对于表面粗糙的金属、塑料件,我倾向用低角度环形光或条形光从两侧打,这样边缘会产生明显的明暗对比;对于高反光材料,则需要用到同轴光或加偏振片。照明设计的目标不是让图像看起来亮,而是让“同一条真实边缘在不同摆放角度下”都能以一致对比度出现,否则边缘提取出的位置会随着目标旋转而变化,把随机的“旋转误差”带进测量里。
2.3 现场“谁是北”的物理基准
很多人会忽略一个看似傻的问题:真正在现场工作的时候,怎么知道相机画面里的正上方就是北?这个问题不解决,算法算出来的角度再准,也是相对于图像坐标系的“假角度”。如果现场有已知经纬度的两点,可以用全站仪或RTK放出两个控制点,在场地里拉一条精确的南北线,然后把相机支架的一个机械边调整到平行于这条线。更简单的做法是用一台经过校准的指北针在支架底座上画出北向线,但这只能保证到0.5度级别,高精度项目里不够。
我自己的做法是做一个带刻度的基准标定块,把它摆到物理北向上,拍一张图存进项目档案。这个标定块的底面贴有一根经过精密铣削的不锈钢直边,直边的方向用全站仪复核过,与真北偏差不超过0.02度。每次系统重启后,先让算法检测这条基准边,算出相机坐标系相对北向的安装偏角,写入参数文本。这个偏角一旦存下来,后续所有目标角度就都能换算成相对北向的真实值。否则今天下午工装被谁撞了一下,相机绕光轴转了一点点,第二天测量结果全部偏差固定值,很难排查。
3. 把像素变成方向角:标定、坐标系与南北-东西向映射
3.1 先搭起四层坐标系,方向才不会算乱
从图像上一个点到你口中的“东西方向X,南北方向Y”,中间隔了四个坐标系:像素坐标系(u,v)、图像物理坐标系(x,y)、相机坐标系(Xc,Yc,Zc)、世界坐标系(Xw,Yw,Zw)。方向测量通常假设目标平面是一个水平面或者工件安装面,于是可以简化成二维问题,但仍建议至少把外参关系写清楚。
世界坐标系定义我固定为:Zw轴竖直向上,Xw轴指向东,Yw轴指向北。这样任何一个边缘向量在世界坐标系里的分量(dxw, dyw),它的方位角就能直接利用atan2(dyw, dxw)算出,其中正北对应0度,正东对应90度。尽量不要把Xw指向北、Yw指向东,虽然数学上完全等价,但项目会议里反复解释“我的X其实是北,Y是东”非常容易让现场工程师晕头转向。
3.2 内参畸变和平面单应矩阵的计算步骤
为了让像素坐标能够准确映射到世界坐标,必须先消除镜头畸变并求出相机相对场地的姿态。传统做法分两步。第一步在实验室或者现场用棋盘格标定板采集15到20张不同角度和位置的照片,调用cv2.calibrateCamera得到相机内参矩阵和畸变系数,这组数据属于相机的固有属性,只要镜头和相机相对位置不变就可以一直用。第二步求解相机外参,也就是相机在世界坐标系里的位置和旋转,我习惯使用cv2.solvePnP,把棋盘格角点的世界坐标和对应图像坐标送进去,得到rvec和tvec。
如果目标测量平面十分平整,而且相机位置相对平面基本固定,则可以直接标定一个单应矩阵H。H的好处是能够把平面上的像素坐标一步映射为世界平面坐标,不需要显式区分内参和外参。我当时在工件方向检测项目里就是这么做的:在工作台面上放一张带编码点的标定板,让编码点中心的世界坐标已知,检测到像素坐标后用cv2.findHomography求H,然后存成XML文件。运行时所有角点像素坐标都经H变换到世界坐标,再做方向计算。
有一个重要细节:单应矩阵只在“目标表面和标定板所在平面完全重合”时成立。如果被测工件有一定高度,它表面的点映射到世界坐标时就会有透视偏移。针对薄片类工件,问题不大;厚料工件则必须用solvePnP加高度补偿,甚至引入三维点参与拟合。
3.3 从世界坐标向量到光电方向的最终角度
当你得到了边缘上两个点在世界坐标系中的坐标A(x1, y1)和B(x2, y2),边缘方向角就可以表示为atan2(y2 - y1, x2 - x1)算出来的是一个相对东西轴的旋转角,需要再转换成“相对北方向”的方位角。由于我定义世界坐标的X轴指东、Y轴指北,一个边缘向量朝正北时角度是0度,朝正东时角度是90度,那么转换公式就是:angle = fmod(90 - atan2(dy, dx) * 180 / PI + 360, 360)。在程序里我更推荐把向量先归一化,再统一按这个公式换算成0到180度的主方向值。
代码里通常还要处理一个常见的二义性:一条180度的线段和一条0度的线段方向其实相同。在测量长条形工件时,算法输出90.2度还是输出90.2度加180度没有意义,我一般在输出前对角度取模180,这样后续判定产品是否在合格范围内时会省掉很多特殊判断分支。
4. 方向线提取的算法主干:从边缘到正交主轴线
4.1 预处理和边缘增强的实操套路
图像进入算法后,先不急着做方向拟合,应做一系列保证边缘质量的处理。第一是降噪,我常用5x5或7x7的中值滤波,它能去掉孤立噪点同时不把边缘弄模糊,这比高斯滤波更适合工业边缘检测。第二是灰度和对比度调整,一般用直方图均衡化或局部自适应方法,让低对比度工件边缘的灰度梯度变得足够大。
在依赖直线检测的项目中,接着用Canny算子提取边缘。Canny的两个阈值参数很关键,高阈值建议设为低阈值的2到3倍,避免提取出过多纹理噪点。如果现场环境杂光比较严重,我会在图像预处理前先做一个背景减除,把静止的台面纹理扣掉,只保留移动的工件和它边缘。背景减除的模型用MOG2就能满足大多场景,处理不了的场景就去现场调光源和遮光,而不是继续堆算法。
有时边缘出现在弱纹理区域,比如白色导热垫上的浅色刻线,Canny基本无能为力。这时方向提取可以改成先做区域分割,用自适应阈值把导热垫区域从背景中分离出来,求最小外接矩形。实测下来,与其执着于“检测到直线”,不如学会判断目标是什么形态:有清晰亮暗边界的就用边缘拟合,区域边界更稳定就用区域轮廓拟合。
4.2 提取东西向边和南北向边时的策略差异
很多目标物本身是矩形,有两条互相垂直的边缘族。把其中一族称为“南北向主边”,另一族称为“东西向主边”。算法上我分成两步处理,而不是一步到位拉一条长直线。先对图像提取所有轮廓中面积最大的外轮廓,用Douglas-Peucker算法简化为一个四边形;然后判断四个顶点构成的四条边哪一条最接近世界坐标下的北向。每一条边都求出它中心的坐标和方向角,把方向角落在0到90度区间内的边归到“北偏东组”,落在90到180度区间内的边归到“东偏北组”,再对两组分别做方向平均。
这样分类的原因很直观:通常安装在场地里的矩形目标不会旋转超过90度,一条“原本应该南北走向”的边,在现场可能因为工件摆歪而变成北偏东10度,也可能变成北偏西10度,但绝对不会突然跑到80度附近去。如果遇到工件可能任意旋转的情况,就优先把矩形轮廓的长边定义为主特征边,但前提是现场确认过“长边应大致沿南北”。实际应用里必须固定这个语义,否则同一个工件转90度后算法会认为主方向变化了90度,而机械手可能期待的是“翻转逻辑”。
处理完两组直线后,我还会输出两个偏差角:南北向偏差和东西向偏差。东西向偏差等于东向组平均方向减去90度,用于衡量目标的短边方向是否也和理论东西向对齐。对一个受控的矩形工件来说,这两个偏差理论上应该相等,因为矩形自身正交,但由于图像噪声、透视、光照,两个值可能差出0.1到0.2度。正是这种不一致让我引入了下一节的正交性修正。
4.3 亚像素边缘和RANSAC拟合让稳定度上一个台阶
只靠像素级边缘拟合方向,精度很容易被像素量化限制在0.15度左右,高要求项目里不够用。做0.05度级别的方向测量时,我会先把边缘精度推到亚像素。简单做法是:沿边缘的法线方向取一个灰度剖面,对灰度值做插值或拟合,找到灰度梯度极值点的亚像素坐标;把大量亚像素边界点送到RANSAC直线拟合器里,剔除离群点,再对剩余内点做整体最小二乘直线拟合。这个流程能把同一条边的重复检测标准差从0.1度压到0.02度。
RANSAC直线拟合的迭代次数要够,否则小概率会把离群点选成种子。我常用的参数是:置信度0.99,每次随机取2个点确定直线,距离阈值设为0.3像素。内点数量少于全部边界点一半时,我会认为边缘被遮挡或破边严重,这时宁可不输出方向,也不要拿截断残缺的边估算整个目标姿态,否则下游机械手抓偏了算谁的。
4.4 正交性修正:为什么直接拿两条直线容易自相矛盾
这里我想专门提一个很容易踩的坑:即使图像里真的检测到了两条斜交的直线,直接输出它们的各自角度也不一定是最终答案。对刚性矩形工件来说,南北向边和东西向边在物理上必须严格垂直,如果算法的两个输出角度差不是90度,说明至少有一条边的方向估歪了,或者透视校正不到位。把两族方向直接扔给上位机,它两边对不齐就会报警。
我采用的修正策略叫“主方向加权正交化”。先选出较长的、质量更好的一族作为主方向,比如南北向边;把较短的东西向边方向整体投影到“主方向加90度”的方向上。投影操作等于强行让两条边保持垂直,短边族的原始测量值只用于修正主方向上的残余旋转,不再独立输出自己的绝对角度。这样做会损失一部分短边的真实信息,但在受控矩形场景中,真实世界的约束比图像里的噪声可靠得多。
python复制import numpy as np
def rect_orientation(angle_main_deg, angle_secondary_deg):
# 将主方向归一到 [0, 180) 区间
main_dir = np.deg2rad(angle_main_deg % 180)
# 东西向边理论应垂直主方向
expected_secondary = main_dir + np.pi / 2
# 用等角度插值把二者差距平均分配,避免图像噪声单独影响一个方向
measured_secondary = np.deg2rad(angle_secondary_deg % 180)
# 求两个角度差,映射到 [-pi/2, pi/2] 范围后做加权平均
delta = np.angle(np.exp(1j * (measured_secondary - expected_secondary)))
corrected_main = np.rad2deg(main_dir + 0.5 * delta)
return corrected_main % 180
这段代码的思路是用夹角偏差的一半来微调主方向,而不是粗暴地把主方向当成唯一真值。在我的经验里,如果短边质量其实很好、只是被边缘噪声拖累了,这种加权会让两个方向输出互相牵制,精度比只信长边更稳。非刚性物体就不建议用这个强制正交逻辑,因为自然界里没有物理规律保证墙面、道路边缘一定成直角。
5. 精度不是写出来的:重复性测试与偏差收敛现场记录
5.1 搭建一个能被复核的精度测试环境
做方向测量系统,最忌讳直接拿尺子量一下就说准。要验证系统,需要一个真值基准。现场没有高精度转台时,我会带一台二手但精度足够的精密分度头或光学平台用旋转台,把被测件放在台面上,旋转台刻度到0.01度。用一个带有直边的标准长方体作为被测物,长边大于100mm,表面做哑光处理,避免反光影响。
测试流程是:先把标准件的长边对准世界坐标的北向,记为0度基准;从0度开始,每隔15度旋转一次,一直测到180度;每个角度位置连续拍10帧,算法输出10个方向角,记录它们的平均值和标准差。通过标准差能了解系统的重复性,通过平均值和转台真值的差能了解系统的绝对偏差。绝对偏差通常包含系统安装误差、标定误差和光照不对称误差,是比较难通过简单算法消除的。
5.2 我实测的一组代表性数据
拿我最近一个检测项目举例,500万像素黑白相机,12mm低畸变镜头,工作距离450mm,视场350mm乘260mm。目标是一个边长约80mm的哑光黑色铝块。经过完整标定后,算法输出的角度数据如下:
| 转台真值(度) | 算法输出均值(度) | 重复性标准差(度) | 均值偏差(度) |
|---|---|---|---|
| 0 | 0.04 | 0.022 | +0.04 |
| 15 | 15.10 | 0.026 | +0.10 |
| 30 | 30.06 | 0.019 | +0.06 |
| 45 | 45.18 | 0.034 | +0.18 |
| 60 | 60.12 | 0.025 | +0.12 |
| 75 | 74.91 | 0.021 | -0.09 |
| 90 | 90.15 | 0.027 | +0.15 |
重复性明显能达到0.05度以内的设计指标,但绝对偏差在45度附近达到0.18度,这个量级足够让某些严格客户不满意。仔细排查后发现,问题不是相机本身,而是转台所在台面不是一个理想水平面,有约0.1度的倾斜,导致标准件旋转到不同角度时投影方向略被压缩,输出角度呈现周期性偏差。用水平仪调平台面后,45度处的偏差降到0.06度。
5.3 偏差收敛的常见策略顺序
遇到绝对偏差偏大,我建议按固定顺序排查,而不是先怀疑算法。第一查机械基准,用水平仪确认测试平面水平;第二查标定板方向,确认标定板直边没有偏离真北超过预估值;第三查镜头畸变校正是否真的生效,可以在图像边缘拍一条很长的直线,看拟合后的直线弯曲程度;最后才查直线拟合算法里的亚像素处理。
我曾经遇到过一个很隐蔽的问题:光源控制器因为电压波动导致亮度缓慢变化,工件边缘被光晕扩宽了约2个像素。表面看亮度变化不影响边缘位置,但实际因为镜头有轻微色差和散射,光晕中心并不一定落在真实边缘上,不同亮度条件下直线拟合结果相差0.1度。后来给光源控制器加了稳压措施,并在每次测量前用相机自动曝光控制一个固定灰度平均值,问题才彻底消失。方向测量这类任务的精度能不能达标,往往取决于这些看似和“方向”无关的光学细节。
6. 现场调试我踩过的几个坑:反光、环境光和安装倾角
6.1 高光表面的镜面反射会把边缘“推走”
有一段时间我在做金属外壳的壳体方向检测,外壳是高光铝,表面像镜子一样。低角度打光时,周围环境里的灯管、货架、人都会在铝壳表面形成镜像,算法抓到的边缘有时是外壳真实外边界和镜面像的叠加。工件稍微一旋转,镜像变化非常剧烈,导致同一个工件放10次,角度输出能跳0.5度,我当时在调试记录里写满了问号。
最终解决思路是给相机加偏振片和偏振光源,在光源前装一块线偏振片,镜头前再加一块偏振方向相互垂直的分析片,这样能够滤掉一次镜面反射光。糙面产生的漫反射光会保留一部分进入相机,但镜面反射几乎被消除。加了偏振后,图像上的高光斑点大幅度减少,角度波动降到0.05度以内。偏振片的缺点是会降低整体亮度,需要同步增强光源功率,而且偏振方向要求旋转到合适角度,所以现场调起来也要花点时间。
6.2 环境光干扰为什么用遮光罩比软件滤波更省心
方向测量的边缘位置对亮度变化极其敏感,因为边缘检测大多基于梯度极大值,不均匀的杂散光会在低反差边缘附近制造假的灰度峰。室外或者半开放产线上,下午阳光从一个方向照入,能让工件表面形成明显的渐变阴影,算法检测到的主边界位置随阴影强度变化,输出的角度自然飘。
软件上可以做的补救有限,比如用频率滤波把阳光造成的低频亮度渐变去掉,但效果不如物理手段。我在这种现场坚持加装一个矩形的半封闭遮光罩,相机和光源都挂在罩内,工件从下方通过,只留一个大约100mm高的开口给传送带。这样一个简单的铝型材骨架加遮光布结构,成本不高,却能屏蔽掉90%的环境光变化。实际项目里常有调试人员连续调了两周环境光漂移,最后加个罩子半天就好,所以我现在的原则是软件尽力,物理先行。
6.3 安装倾角:一个最容易被忽略的系统级误差
相机安装时很难做到完全垂直于被测平面,稍微有俯仰或左右倾斜,就会产生一个类似“透视缩短”的效果:一个真实角度为45度的边,在未校正的图像里可能测出43度或47度,而且不同角度位置误差不一样。这个误差不是机械上把相机“垫平”就能完全消除的,因为每次拆装相机后微小变动都会引入新误差。
正确做法不是试图手动把相机拧到垂直,而是直接做外参标定,让算法知道相机相对水平面的精确姿态。利用棋盘格标定板放置在被测平面的几个位置,解出旋转向量后,用图像上的直线反投影到水平面上去做方向计算,这样安装倾角就能被数学消除。如果你已经开始做单应矩阵映射,那本质上已经隐含做了这个校正,只要标定板摆放时没有人为倾斜,输出角度就能稳定下来。最怕的是现场工程师为了追求“看起来垂直”,反复去拧相机支架,却忘了标定参数已经变了,新的安装姿态还没重新标定,这种隐藏矛盾排查起来特别费劲。
6.4 参数固化与快照存档是算法维护的救命稻草
方向检测系统在交付后,现场常常会调整光圈、更换工件或者移动相机位置。每次改动如果不同步更新参数文件,总有一天运行结果会莫名变差。我习惯把相机内参、畸变系数、单应矩阵、光源亮度档位、感兴趣区域坐标通通存放在同一个配置文件里,并在控制界面提供“保存当前配置快照”的按钮。每完成一次标定和调试,就导出一份带日期的快照存档。
调过方向测量系统的人都懂,如果只记录算法代码、不记录现场标定状态,出问题时几乎无法定位是算法缺陷还是环境变化。做项目归档时,一定要把“标定记录”当作和源代码同等重要的交付物,标明谁在什么时间、用什么工装、做了哪一组标定,这样即使半年后再接手,也能快速恢复状态。哪怕标题只有一行“提取光学南北-东西向”,文件夹里也应当保留完整的现场照片和参数备份。
7. 另一种常见延伸:光学影像里提取方向线要素的工程思路
7.1 当被测对象不是工件而是影像地图时怎么处理
如果“光学南北-东西向”落到遥感或测绘领域,它解决的问题就变成:在一张经过几何校正的光学影像里,自动提取出所有道路、建筑物或田埂的走向,然后把线段的方向归入南北向组和东西向组,用于分析城市路网或农田排列。
这种任务在算法层面和前面的工业检测很像,都是“检测直线-求角度-分类”。但有个额外关卡必须是地理配准质量。拿到一张无人机正射影像时,必须确认影像已采用投影坐标系存储,某些影像服务返回的是经纬度坐标,直接用经纬度差值算方向会产生严重形变,尤其在高纬度地区更明显。我建议在项目里先将影像重投影到本地高斯投影或UTM坐标系,然后再做线方向提取。
7.2 面向不同对象的直线方向统计流程
如果目标是建筑物,可以先做屋顶轮廓提取,再用轮廓拟合出主方向轴;如果目标是道路,要采用线段检测算子提取道路中线或道路边缘,然后剔除过短的线段,只统计长度超过设定阈值的对象。
一份简化处理代码的思路和工业方向检测非常接近,唯一区别是在最后把“图像角度”变成了“投影方向角”。
python复制import numpy as np
from sklearn.cluster import KMeans
def dominant_directions(segments_xy, max_angle=180.0):
# 输入线段起终点X,Y坐标,单位为米
angles = []
weights = []
for (x1, y1, x2, y2) in segments_xy:
dx = x2 - x1
dy = y2 - y1
length = np.hypot(dx, dy)
if length < 2.0:
continue
ang = np.rad2deg(np.arctan2(dy, dx)) % max_angle
angles.append(ang)
# 角度的权重不宜直接用长度,因为极长线段会掩盖其他主方向
weights.append(np.log1p(length))
if len(angles) < 10:
return np.array([]), np.array([])
angles = np.asarray(angles).reshape(-1, 1)
km = KMeans(n_clusters=min(4, len(np.unique(angles))), n_init=10)
labels = km.fit_predict(angles)
dom = []
for k in range(km.n_clusters):
cluster_angles = angles[labels == k]
mean_ang = np.rad2deg(np.angle(np.mean(np.exp(1j * np.deg2rad(cluster_angles)))))
dom.append(mean_ang % max_angle)
return np.sort(dom), np.bincount(labels, minlength=km.n_clusters)
这段代码里我把角度当成圆环数据求平均,而不是直接求算术平均值。原因是一条89度的线和一条91度的线,算术平均是90度没问题;但如果聚类中心出现在179度和1度附近,算术平均会算出90度这种离谱结果。用单位圆上的向量求和取相位,可以正确处理这种环状角度。
7.3 输出成果应该包含的不只是两个方向
在前面的场景里,用户往往期待系统把南北向道路总长度、东西向道路总长度、各自所占比例,以及偏离正方向的角度直方图都输出出来,只返回一个南北和东西的平均值不够用。工程项目里,这些统计结果通常要交给规划或质检部门做第三方验证。所以我在输出结果里会额外生成一张带方向的专题图,用不同颜色区分南北向线段组和东西向线段组,并叠加上地理坐标网格。这样的可视化让非专业人员也能一眼判断算法提取有没有错位,比纯数字表直观得多。
从工业现场方向检测到遥感线方向统计,名称都是“提取光学南北-东西向”这一类,但共通的底层原则其实就是三句话:先把“方向”的基准定义到物理世界,而不是留在图像里;再用尽量准确的标定把光学坐标系和场地坐标系对齐;最后在算法端充分挖掘目标本身的几何约束,比如正交边、平行边、长边优先等。回头再看那个只有七个字的项目名,反而有种莫名的清爽,因为它把这几件事压缩到了极致,提醒我每一次测量都要回到对基准和物理约束的敬畏上。
