【C++】直线拟合与跟踪详解与实现摘要:本文讲解如何用 C++ 与 OpenCV 实现直线拟合与跟踪,通过 Line 对象封装线段从拟合、增量生长到合并的完整状态机,并借助 Hough 投票校正方向漂移,附完整可运行代码与参数调优指南。博客长期更新,本文最近一次更新时间为:2026年9月22日。在图像/点云的处理流水线里,常常得到的是一堆过拟合的、有方向漂移的、离散的点集——比如激光条纹中心线、焊缝边缘点、雷达目标点。我们真正想要的是一条稳定、连续、可继续生长的直线段:先拟合出这条线的姿态,再逐帧把新点"焊"上去,同时用 Hough 投票修正漂移的方向。这就是本文要讲的"直线拟合与跟踪"——用一条Line对象封装整条线段从生到死的状态机。一、问题拆解:直线跟踪到底在跟踪什么?几乎所有逐帧提取算法输出的都是无身份的离散点集:这一帧在(x,y)(x,y)(x,y)有一堆点,下一帧又是一堆点,算法并不知道它们是否属于同一条直线、属于哪条直线。直线跟踪要解决的是这样一个复合问题:拟合(Fit):给一堆点,得到一条直线的规范表达(不依赖具体采样密度的(Ax+By+C=0)(Ax+By+C=0)(Ax+By+C=0))。生长(Grow):新帧来了新点,把它们追加到已有线段上,并重新拟合以吸收新信息——而不是每帧从头重来。校正(Correct):短点列拟合出的方向极易漂移,用局部 Hough 投票得到更稳的主导方向来对齐或合并线段。判终点(State):线段两端到底是真实端点,还是连接的缺口、跳变、虚拟外推点?这决定了后续能否把多段线段串成一条完整长线。这四个职责如果没有清晰边界,很容易写成一团乱的"线性拟合 + 一堆 if"。下面用一套Line类把它们串起来。二、核心数据结构:一条线,从生到死把一条"正在跟踪的直线段"建模成一个对象,它同时保存几何(拟合结果、端点、构成点)与语义状态(两端各是什么性质的边界)。这是整套代码的骨架:// line.h —— 直线段对象:几何 + 语义状态#ifndefLINE_H#defineLINE_H#includeopencv2/core.hpp#includevectornamespacealg{// 一条可增长、可标记状态的直线段classLine{public:Line(){}// ---- 几何核心 ----cv::Vec3d line;// 直线参数 (A, B, C),满足 A*x + B*y + C = 0cv::Point2f beginPoint,endPoint;// 段的真实起点 / 终点std::vectorcv::Point2fpoints;// 构成这条段的点序列(顺序即生长顺序)// ---- 派生量 ----floattheta;// 直线倾角,单位度([-90,90) 归一化)// ---- 语义状态:两端点各是什么 ----// begin / end 两端的性质标记,驱动"能否继续拼线"的判断boolisBeginConnectedPoint=false;// 与上一条线段衔接boolisBeginVirtualPoint=false;// 虚拟外推点(无真实观测)boolisBeginCrossPoint=false;// 与相邻线段交叉boolisBeginJumpPoint=false;// 该处存在跳变(断裂)boolisEndConnectedPoint=false;boolisEndVirtualPoint=false;boolisEndCrossPoint=false;boolisEndJumpPoint=false;public:intlength()const;// 段的纵向跨度(像素)voidrefresh();// 用 points 重新拟合,更新 line/端点/thetavoidmergeNext(constLinel);// 追加另一线段后重新拟合(增量生长)voidupdateThetaByHough();// 用局部 Hough 投票校正主导方向};}// namespace alg#endif设计要点:用points保存原始采样,而非只存拟合参数——因为增量拟合需要回看所有点,且后续拼线需要真实端点坐标。用isXXX标记端点语义,而不是用魔数或注释——这是把"业务判断"从"几何计算"里剥离开的关键,也是这套Line最有价值的地方。参数统一走cv::Vec3d (A,B,C)与theta两个派生量,所有使用者(合并、排序、拼线)都读这两个,避免各算各的。三、Code 1:规范化的最小二乘拟合refresh()cv::fitLine返回的是Vec4f(归一化方向vx,vy, 线上一点x0,y0),需要转成标准直线方程并做符号归一——否则同一条线的参数可能因方向相反出现两套 (A,B,C),合并时对不上。归一规则统一为"让 A 恒为正":// 用当前 points 重新拟合,并把直线方程规范化为 A*x+B*y+C=0 且 A=0voidLine::refresh(){if(points.size/