之前見過別人利用halcon封裝了一個不錯的函數叫drawRake好像是這個名字。這個工具挺好用的,可以在圖像上隨意畫一條直線,然后設置一些參數,他就能在你畫的這條線附近尋找你想要的直線, 然而其不是開源的,halcon也是收費的。於是我就心血來潮想自己做一個類似的工具,花了一天搞出來了,經過測試,效果還是杠杠的。下面介紹給大家,並會提供該工具函數的源碼。
圖像處理過程中我們有時候要對攝像頭采集的圖像進行直線分析,如果利用opencv分析的話,我們常常要自己建個工程,然后利用一些檢測直線的算法,比如霍夫變換等,然而這樣比較耗時。這個工具就可以即時的對圖像直線進行分析。接下來給出這個工具函數的原型。名字我也取為drawRake。如下:
-
std::vector<cv::Point2d> drawRake( cv::Mat &f,
-
cv::Point2d pStart,
-
cv::Point2d pEnd,
-
int gap,
-
int searchLength,
-
int threshValue,
-
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