zoukankan      html  css  js  c++  java
  • 图像直线分析和拟合工具——opencv

     之前见过别人利用halcon封装了一个不错的函数叫drawRake好像是这个名字。这个工具挺好用的,可以在图像上随意画一条直线,然后设置一些参数,他就能在你画的这条线附近寻找你想要的直线, 然而其不是开源的,halcon也是收费的。于是我就心血来潮想自己做一个类似的工具,花了一天搞出来了,经过测试,效果还是杠杠的。下面介绍给大家,并会提供该工具函数的源码。

            图像处理过程中我们有时候要对摄像头采集的图像进行直线分析,如果利用opencv分析的话,我们常常要自己建个工程,然后利用一些检测直线的算法,比如霍夫变换等,然而这样比较耗时。这个工具就可以即时的对图像直线进行分析。接下来给出这个工具函数的原型。名字我也取为drawRake。如下:

    1.  
      std::vector<cv::Point2d> drawRake( cv::Mat &f,
    2.  
      cv::Point2d pStart,
    3.  
      cv::Point2d pEnd,
    4.  
      int gap,
    5.  
      int searchLength,
    6.  
      int threshValue,
    7.  
      bool isJudgeByGreatThan = false);


    参数解析:

    cv::Mat &f:图像数据,须为3通道或者单通道图像。

    cv::Point2d pStart:指定直线的起点(这个是我们指定的,直线的起点和终点将被指定为搜索区域,我下面做的这个软件使用鼠标画线来指定直线)。

    cv::Point2d pEnd:指定直线的终点。

    int gap:搜索间隙(指定直线的细分步长)。

    int searchLength:搜索长度,搜索范围。

    int threshValue:搜索阈值。

    bool isJudgeByGreatThan:目标点是否判决于大于阈值

    返回值:std::vector<cv::Point2d>:一些列目标点集,用于直线拟合。

    ------------------------------------------------------------------------------------------

            可能不大好理解这几个参数的定义,先来看看下面几个直观的图像吧!如下图:是摄像头实时采集的一帧图像数据,然后我们现在要分析下图黑色箭头所示的直线。

            我们只需要在图像上的该直线附近画一条差不多直线,这条画上去的直线就是上面的输入参数的cv::Point2d pStart,cv::Point2d pEnd。效果如下图所示,因为是相机的实时帧,所以他会实时分析该直线,可以调节gap,searchLength,threshValue,isJudgeByGreatThan来分析最适合的直线。

             如上图,我们看到一堆的小箭头,那个就是点集的搜索方向。现在看到的那一堆箭头比较密集是由于gap值比较小的原因。如果我们把gap变大,就会看到这一群箭头变得稀疏了,如下图所示:

            我们看到箭头变稀疏了,因为gap增大了,我们还可以改变箭头的长度,他的意义就是输入参数searchLength,即搜索范围,他会从箭尾搜索到箭头,知道索引到符合的点,我们可以增大searchLength,就可以增加搜索范围,相应的箭头也会变长,如下图:

            我们再分析一下别的直线,也很容易的找到该直线如下所示:

            我们再来分析一下输入参数isJudgeByGreatThan,如果是FALSE的话就是从亮到暗(阈值是下限)搜索,如上图,如果设置成TRUE的话,就是从暗到亮搜索(阈值是上限),比如我们来分析一下纸箱盖子的直线,如下图。

            好了,差不多直观的了解完了,接下来就是上代码。

    ------------------------------------------------------------------------------------------

    下面是drawRake函数源码:

    std::vector<cv::Point2d> drawRake(cv::Mat &f, cv::Point2d pStart, cv::Point2d pEnd, int gap, int searchLength, int threshValue, bool isJudgeByGreatThan){
     
            // 初始化内存
            std::vector<cv::Point2d> pVec; pVec.clear();    // rake点集
            double k = 0., b = 0., xConst = 0.;                // 直线方程参数
            int lineLength = 0;                                // 直线长度
            double lineAngle = 0.;                            // 直线角度
            double opposite = 1.0;                            // 直线的方向是否相反 是:为-1.0,否:1.0
            int imageChs = f.channels();                    // 图像通道数,支持3通道和单通道图像
            cv::Mat singleChImage;                            // 单通道图像
     
            // 有效性判断
            if (f.empty()) return pVec;
            if (pStart == pEnd) return pVec;
     
            // 获取单通道图像
            if (imageChs == 1){
                //f.copyTo(singleChImage);
                singleChImage = f;
            }else if (imageChs == 3){
                cv::cvtColor(f, singleChImage, CV_BGR2GRAY);// 默认的单通道是灰度空间
            }
     
            // 获取直线方向性
            if (pEnd.x < pStart.x) opposite = -1.0;
     
            // 获取直线方程 y = k * x + b, y = b, x = const value.
            if ((pEnd.x - pStart.x) == 0.){
     
                xConst = pStart.x; // x = const value.
                lineAngle = 90.;
                if (pEnd.y < pStart.y) opposite = -1.0;        // 只有这种情况直线方向性考虑Y
            }else{
     
                k = (pEnd.y - pStart.y) / (pEnd.x - pStart.x);
                b = pStart.y - k * pStart.x;
                lineAngle = atan(k) * 180. / CV_PI;
            }
            lineLength = sqrt(pow((pEnd.x - pStart.x), 2.) + pow((pEnd.y - pStart.y), 2.));
     
            // 细分直线,细分步长位gap,获取每一个细分点的坐标
            std::vector<cv::Point2d> gapPVec; gapPVec.clear();
            double Xgap, Ygap;
            for (int g = 0; g <= lineLength; g += gap){
     
                Xgap = pStart.x + (opposite * g * cos(lineAngle * CV_PI / 180.));
                Ygap = (pEnd.x == pStart.x) ? (pStart.y + opposite * g) : (k * Xgap + b);
                gapPVec.push_back(cv::Point2d(Xgap, Ygap));
            }
     
            // 找每个细分点的直线段,直线段长度就是搜索长度范围,由searchLength指定,这边计算出来的lineAngle属于(-90 ~ 90度)
            double halfSearchLength = searchLength / 2.;
            double xLower, xUpper, yLower, yUpper;            // 细分点的直线段在X-Y轴投影的横坐标上限和下限
            double Knew = 0., Bnew = 0., XnewConst = 0.;    // 细分点的直线段的k和b    和x = const直线
            bool newLineIsVecticalLine = false;                // 默认是一般直线,非竖线
            std::vector<cv::Point> lineArrowVec[2];            // 存取用于画结果的直线标
            if (k == 0.){
                newLineIsVecticalLine = true;
            }else{
                if ((pEnd.x == pStart.x)) Knew = 0.;
                else Knew = -1. / k;
            }
            for (int i = 0; i < gapPVec.size(); i++){
     
                if (abs(lineAngle) < 45.){                    // 小于45度的情况
                    
                    if (newLineIsVecticalLine){ // x = const
     
                        XnewConst = gapPVec[i].x;
                        yLower = gapPVec[i].y - halfSearchLength;
                        yUpper = gapPVec[i].y + halfSearchLength;
                    }
                    else{                        // b = y - k * x
     
                        Bnew = gapPVec[i].y - Knew * gapPVec[i].x;
                        yLower = gapPVec[i].y - halfSearchLength * cos(abs(lineAngle * CV_PI / 180.));
                        yUpper = gapPVec[i].y + halfSearchLength * cos(abs(lineAngle * CV_PI / 180.));
                    }
                    // 上边到下边的直线递增查询,下边到上边的直线递减查询
                    for (int yy = yLower; yy <= yUpper; yy++){
     
                        int xCur = 0, yCur = 0;
                        if (pStart.x > pEnd.x) yCur = yUpper - yy + yLower;
                        else yCur = yy;
                        xCur = (newLineIsVecticalLine) ? (XnewConst) : (yCur - Bnew) / Knew;
     
                         if (xCur > singleChImage.cols || xCur < 0 || yCur > singleChImage.rows || yCur < 0) continue;
     
                        unsigned char *pdata = singleChImage.ptr<unsigned char>(yCur);
                        if (isJudgeByGreatThan){
     
                            if (pdata[xCur] > threshValue){
     
                                pVec.push_back(cv::Point(xCur, yCur));
                                break;
                            }
                        }
                        else{
     
                            if (pdata[xCur] < threshValue){
     
                                pVec.push_back(cv::Point(xCur, yCur));
                                break;
                            }
                        }
                    }
     
                    double F_yLower, F_yUpper;
                    if (newLineIsVecticalLine){
                        F_yLower = XnewConst;
                        F_yUpper = XnewConst;
                    }else{
                        F_yLower = (yLower - Bnew) / Knew;
                        F_yUpper = (yUpper - Bnew) / Knew;
                    }
                    if (pStart.x < pEnd.x){
                        lineArrowVec[0].push_back(cv::Point(F_yLower, yLower));
                        lineArrowVec[1].push_back(cv::Point(F_yUpper, yUpper));
                    }
                    else{
                        lineArrowVec[1].push_back(cv::Point(F_yLower, yLower));
                        lineArrowVec[0].push_back(cv::Point(F_yUpper, yUpper));
                    }
     
                }else{                        // 大于45度的情况
     
                    Bnew = gapPVec[i].y - Knew * gapPVec[i].x;
                    xLower = gapPVec[i].x - halfSearchLength * sin(abs(lineAngle * CV_PI / 180.));
                    xUpper = gapPVec[i].x + halfSearchLength * sin(abs(lineAngle * CV_PI / 180.));
     
                    // 左边到右边的直线递增查询,右边到左边的直线递减查询
                    for (int xx = xLower; xx <= xUpper; xx++){
     
                        int xCur = 0, yCur = 0;
                        if (pStart.y > pEnd.y) xCur = xUpper - xx + xLower;
                        else xCur = xx;
                        yCur = Knew * xCur + Bnew;
     
                        if (xCur > singleChImage.cols || xCur < 0 || yCur > singleChImage.rows || yCur < 0) continue;
     
                        unsigned char *pdata = singleChImage.ptr<unsigned char>(yCur);
                        if (isJudgeByGreatThan){
     
                            if (pdata[xCur] > threshValue){
     
                                pVec.push_back(cv::Point(xCur, yCur));
                                break;
                            }
                        }else{
     
                            if (pdata[xCur] < threshValue){
     
                                pVec.push_back(cv::Point(xCur, yCur));
                                break;
                            }
                        }
                    }
                    double F_xLower = Knew * xLower + Bnew;
                    double F_xUpper = Knew * xUpper + Bnew;
                    if (pStart.y > pEnd.y){
     
                        lineArrowVec[0].push_back(cv::Point(xUpper, F_xUpper));
                        lineArrowVec[1].push_back(cv::Point(xLower, F_xLower));
                    }else{
                        lineArrowVec[1].push_back(cv::Point(xUpper, F_xUpper));
                        lineArrowVec[0].push_back(cv::Point(xLower, F_xLower));
                    }
                }
            }// end 'for (int i = 0; i < gapPVec.size(); i++)'
            
            // 画rake点,和搜索方向
            for (int i = 0; i < pVec.size(); i++){
     
                if (imageChs == 1){
     
                    cv::circle(f, pVec[i], 3, cv::Scalar(std::rand() % 255), 3);
                    drawArrow(f, lineArrowVec[0][i], lineArrowVec[1][i], 25, 30, cv::Scalar(std::rand() % 255), 1);
                }
                if (imageChs == 3){
     
                    cv::circle(f, pVec[i], 3, cv::Scalar(std::rand() % 255, std::rand() % 255, std::rand() % 255), 3);
                    drawArrow(f, lineArrowVec[0][i], lineArrowVec[1][i], 25, 30, cv::Scalar(std::rand() % 255, std::rand() % 255, std::rand() % 255), 1);
                }
            }
            // 画所有的细分点的搜索方向
            for (int i = 0; i < gapPVec.size(); i++){
     
                if (imageChs == 1){
     
                    drawArrow(f, lineArrowVec[0][i], lineArrowVec[1][i], 25, 30, cv::Scalar(std::rand() % 255), 1);
                }
                if (imageChs == 3){
     
                    drawArrow(f, lineArrowVec[0][i], lineArrowVec[1][i], 25, 30, cv::Scalar(std::rand() % 255, std::rand() % 255, std::rand() % 255), 1);
                }
            }
     
            return pVec;
    }

    其中有一个drawArrow函数是用来画箭头的,摘自这里,源码如下:

    void drawArrow(cv::Mat& img, cv::Point pStart, cv::Point pEnd, int len, int alpha,cv::Scalar& color, int thickness, int lineType){
     
            cv::Point arrow;
            // 计算 θ 角(最简单的一种情况在下面图示中已经展示,关键在于 atan2 函数,详情见下面)   
            double angle = atan2((double)(pStart.y - pEnd.y), (double)(pStart.x - pEnd.x));
            cv::line(img, pStart, pEnd, color, thickness, lineType);
            // 计算箭角边的另一端的端点位置(上面的还是下面的要看箭头的指向,也就是pStart和pEnd的位置) 
            arrow.x = pEnd.x + len * cos(angle + CV_PI * alpha / 180.);
            arrow.y = pEnd.y + len * sin(angle + CV_PI * alpha / 180.);
            cv::line(img, pEnd, arrow, color, thickness, lineType);
            arrow.x = pEnd.x + len * cos(angle - CV_PI * alpha / 180.);
            arrow.y = pEnd.y + len * sin(angle - CV_PI * alpha / 180.);
            cv::line(img, pEnd, arrow, color, thickness, lineType);
    }

    本文转自:https://blog.csdn.net/KayChanGEEK/article/details/77247884?utm_source=app

  • 相关阅读:
    视频:JDBCRDD源码及自定义JDBCRDD的分区策略
    聊聊spark-submit的几个有用选项
    线性求第k大
    汇编基础 第一章_总线
    长度不超过n的连续最大和___优先队列
    ISAP
    次小生成树
    k短路
    求出欧拉回路,欧拉路径
    拓扑排序bfs_dfs
  • 原文地址:https://www.cnblogs.com/eve612/p/14090256.html
Copyright © 2011-2022 走看看