圖像直線分析和擬合工具——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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM