【android opencv学习笔记】Day 32: 直线检测之点集直线拟合

【android opencv学习笔记】Day 32: 直线检测之点集直线拟合 点集直线拟合在霍夫变换检测出直线后我们往往需要更精确的直线位置与方向例如车道线矫正、工业零件边缘校准、文档透视校正等场景。此时就需要用到点集直线拟合通过边缘点集计算出最优直线实现亚像素级精度的直线定位。点集直线拟合核心原理点集直线拟合的核心目标是找到一条直线使点集中所有点到直线的距离误差最小。1. 最小二乘法L2 距离最经典的拟合方法通过最小化点到直线的欧几里得距离平方和求解最优直线min⁡∑i1ndi2 \min \sum_{i1}^{n} d_i^2mini1∑n​di2​其中did_idi​是点(xi,yi)(x_i,y_i)(xi​,yi​)到拟合直线的垂直距离。这种方法计算简单、速度快但对离群点噪声点非常敏感一个异常点就会严重影响拟合结果。2. M 估计鲁棒拟合OpenCV 的fitLine函数默认采用基于 M 估计的鲁棒拟合算法核心改进是引入权重系数权重与点到直线的距离成反比迭代计算加权最小二乘逐步降低离群点的影响支持多种距离函数L1、L2、Huber、Tukey 等适配不同噪声场景。3. 完整流程从图像边缘到拟合直线边缘提取用 Canny 算法获取图像边缘二值图点集筛选根据霍夫检测的直线提取其邻域内的所有边缘点构建点集直线拟合使用cv::fitLine对筛选出的点集进行鲁棒拟合得到最优直线参数结果绘制根据拟合出的直线参数在原图上绘制精确的直线。OpenCV 核心 API 解析cv::fitLine函数这是 OpenCV 中实现点集直线拟合的核心函数支持二维/三维点集返回直线的方向向量与过点坐标。函数原型voidfitLine(InputArray points,// 输入点集std::vectorPoint / std::vectorPoint3fOutputArray line,// 输出直线参数Vec4f(2D) 或 Vec6f(3D)intdistType,// 距离类型决定鲁棒性doubleparam,// 距离函数参数0表示默认值doublereps,// 精度参数坐标的回归精度doubleaeps// 精度参数角度的回归精度);参数解读distType距离函数类型决定算法的鲁棒性CV_DIST_L2欧几里得距离标准最小二乘法速度快但不抗噪CV_DIST_L1L1 距离对离群点有一定鲁棒性CV_DIST_HUBERHuber 距离对离群点的影响进行衰减CV_DIST_TukeyTukey 距离完全忽略离群点鲁棒性最强。line输出结果二维场景下为Vec4f包含line[0], line[1]单位方向向量(dx,dy)(dx, dy)(dx,dy)line[2], line[3]直线上的一个点坐标(x0,y0)(x_0, y_0)(x0​,y0​)。Android 完整工程实现布局文件 activity_main.xml页面分为原图展示区、Canny 边缘图、点集筛选图、直线拟合结果区使用滚动布局适配大图预览?xml version1.0 encodingutf-8?ScrollViewxmlns:androidhttp://schemas.android.com/apk/res/androidandroid:layout_widthmatch_parentandroid:layout_heightmatch_parentandroid:background#f5f5f5LinearLayoutandroid:layout_widthmatch_parentandroid:layout_heightwrap_contentandroid:orientationverticalandroid:padding10dpandroid:gap10dp!-- 原始图片展示 --LinearLayoutandroid:layout_widthmatch_parentandroid:layout_heightwrap_contentandroid:orientationverticalTextViewandroid:layout_widthwrap_contentandroid:layout_heightwrap_contentandroid:text原始图片android:textSize16spandroid:textStylebold/ImageViewandroid:idid/iv_originandroid:layout_widthmatch_parentandroid:layout_height220dpandroid:scaleTypefitCenterandroid:background#ffffff//LinearLayout!-- Canny 边缘图 --LinearLayoutandroid:layout_widthmatch_parentandroid:layout_heightwrap_contentandroid:orientationverticalTextViewandroid:layout_widthwrap_contentandroid:layout_heightwrap_contentandroid:textCanny 边缘图android:textSize16spandroid:textStylebold/ImageViewandroid:idid/iv_cannyandroid:layout_widthmatch_parentandroid:layout_height220dpandroid:scaleTypefitCenterandroid:background#ffffff//LinearLayout!-- 直线拟合结果 --LinearLayoutandroid:layout_widthmatch_parentandroid:layout_heightwrap_contentandroid:orientationverticalTextViewandroid:layout_widthwrap_contentandroid:layout_heightwrap_contentandroid:text直线拟合结果android:textSize16spandroid:textStylebold/ImageViewandroid:idid/iv_fitlineandroid:layout_widthmatch_parentandroid:layout_height220dpandroid:scaleTypefitCenterandroid:background#ffffff//LinearLayout/LinearLayout/ScrollView上层 Kotlin 代码 MainActivity.kt负责加载本地图片、创建位图、调用 JNI 原生方法、展示结果。开发者只需将自己的 2048×2048 图片放入res/drawable目录修改资源名即可使用packagecom.example.linefitimportandroid.graphics.Bitmapimportandroid.graphics.BitmapFactoryimportandroid.os.Bundleimportandroid.widget.ImageViewimportandroidx.appcompat.app.AppCompatActivityclassMainActivity:AppCompatActivity(){companionobject{init{System.loadLibrary(native-lib)}}/** * JNI 原生方法执行 Canny 边缘检测 点集筛选 直线拟合 * param srcBitmap 输入原图 Bitmap * param outCanny 输出 Canny 边缘图 Bitmap * param outPoints 输出点集筛选图 Bitmap * param outFitLine 输出直线拟合结果 Bitmap */privateexternalfunprocessLineFit(srcBitmap:Bitmap,outCanny:Bitmap,outFitLine:Bitmap)overridefunonCreate(savedInstanceState:Bundle?){super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)// 1. 加载你自己的 2048*2048 图片 valsrcBitmapBitmapFactory.decodeResource(resources,R.drawable.test_image)// 创建输出位图尺寸与原图保持一致valcannyBitmapBitmap.createBitmap(srcBitmap.width,srcBitmap.height,Bitmap.Config.ARGB_8888)valfitLineBitmapBitmap.createBitmap(srcBitmap.width,srcBitmap.height,Bitmap.Config.ARGB_8888)// 2. 调用原生算法 processLineFit(srcBitmap,cannyBitmap,pointsBitmap,fitLineBitmap)// 3. 展示图片 findViewByIdImageView(R.id.iv_origin).setImageBitmap(srcBitmap)findViewByIdImageView(R.id.iv_canny).setImageBitmap(cannyBitmap)findViewByIdImageView(R.id.iv_fitline).setImageBitmap(fitLineBitmap)}}底层 C JNI 代码 native-lib.cpp核心逻辑Bitmap与 OpenCVMat互转、Canny 边缘检测、霍夫线段检测、点集筛选、直线拟合与绘制附带完整注释#includejni.h#includeopencv2/core.hpp#includeopencv2/imgproc.hpp#includeopencv2/highgui.hpp#includeandroid/bitmap.husingnamespacecv;usingnamespacestd;// ------------------------------// Bitmap → Mat// ------------------------------MatbitmapToMat(JNIEnv*env,jobject bitmap){AndroidBitmapInfo info;void*pixelsnullptr;AndroidBitmap_getInfo(env,bitmap,info);AndroidBitmap_lockPixels(env,bitmap,pixels);Matrgba(info.height,info.width,CV_8UC4,pixels);Mat bgr;cvtColor(rgba,bgr,COLOR_RGBA2BGR);AndroidBitmap_unlockPixels(env,bitmap);returnbgr;}// ------------------------------// Mat → Bitmap// ------------------------------voidmatToBitmap(JNIEnv*env,constMatmat,jobject bitmap){AndroidBitmapInfo info;void*pixelsnullptr;AndroidBitmap_getInfo(env,bitmap,info);AndroidBitmap_lockPixels(env,bitmap,pixels);Mat rgba;if(mat.channels()1){cvtColor(mat,rgba,COLOR_GRAY2RGBA);}else{cvtColor(mat,rgba,COLOR_BGR2RGBA);}memcpy(pixels,rgba.data,info.width*info.height*4);AndroidBitmap_unlockPixels(env,bitmap);}// ------------------------------// 核心拟合 画线// ------------------------------voidfitLineProcess(constMatsrc,MatcannyOut,MatfitLineOut){Mat gray;cvtColor(src,gray,COLOR_BGR2GRAY);GaussianBlur(gray,gray,Size(3,3),0);// Canny 边缘Canny(gray,cannyOut,50,150,3);// 收集所有边缘点vectorPointpoints;findNonZero(cannyOut,points);fitLineOutsrc.clone();if(!points.empty()){Vec4f lineParams;fitLine(points,lineParams,2,0,0.01,0.01);floatvxlineParams[0];floatvylineParams[1];floatx0lineParams[2];floaty0lineParams[3];Pointp1(cvRound(x0-vx*2000),cvRound(y0-vy*2000));Pointp2(cvRound(x0vx*2000),cvRound(y0vy*2000));// --------------------------// ✅ line 函数已修复// --------------------------line(fitLineOut,p1,p2,Scalar(0,255,0),// 绿色4// 线宽);}}// ------------------------------// JNI 入口// ------------------------------externCJNIEXPORTvoidJNICALLJava_com_nicoli_hellolinesfitline_MainActivity_processFitLine(JNIEnv*env,jobject thiz,jobject src,jobject outCanny,jobject outFitLine){Mat srcMatbitmapToMat(env,src);Mat cannyMat,fitMat;fitLineProcess(srcMat,cannyMat,fitMat);matToBitmap(env,cannyMat,outCanny);matToBitmap(env,fitMat,outFitLine);}总结与拓展算法核心点集直线拟合通过最小化点到直线的距离误差求解最优直线OpenCV 的fitLine函数支持多种鲁棒距离函数工程流程霍夫检测获取初始直线 → 邻域点集筛选 → 鲁棒直线拟合解决霍夫直线精度不足的问题工程优势本项目完全基于 Android NDK OpenCV 实现支持自定义大图输入源码注释完整可直接用于车道线矫正、文档边缘校准等项目集成拓展方向可在此基础上实现多直线拟合、椭圆拟合fitEllipse、三维点集直线拟合等功能。