文章目錄
- 1 問題及分析
- 2 多尺度霍夫直線 與 漸進概率霍夫線段 細節對比
- 2.1 多尺度霍夫直線 HoughLines
- 2.2 漸進概率霍夫線段 HoughLinesP
- 2.3 HoughLines 和 HoughLinesP 所求結果細節對比
- 2.4 為什么 HoughLinesP 直線兩端沒有呈放射狀態呢?直線總是平行嗎?
- 2.5 HoughLines 直線角度,輸出有順序?
- 3 獲取十字交點坐標
- 3.1 找到兩直線
- 3.1.1 獲得 HoughLines 橫豎兩條線
- 3.1.2 獲得HoughLinesP 橫豎兩條線
- 3.2 計算交點坐標原理 及實現
- 4 項目源碼
- 4.1 .h文件
- 4.2 .cpp文件
- 4.3 main文件
- 5 將 以上兩種封裝成一個函數——新增方法切換,轉換大圖坐標等功能
1 問題及分析
該問題,是由實際工業項目中玻璃出來的,實驗詳細記錄分析,便于日后查看;
(雖然使用的知識點簡單,但要把簡單的知識點 用于解決好 實際工業問題 還是要做很多優化與改進的)
問題:
2000萬像素的工業相機,要對拍攝的畫面中“十字刻度尺”精準定位 (改刻度尺在原圖中占比很小);
原圖很大,下面截取需要定位的ROI區域如下圖 。
難點:圖像較暗,有紅色黃色干擾,刻度尺上有記號筆涂抹干擾,圖像細節模糊;
定位結果
思路 :
- 先對原圖灰度化,
- 再進行反色使刻度尺部分凸顯出來;
- 二值化,找直線,直線篩選,求兩線交點。
2 多尺度霍夫直線 與 漸進概率霍夫線段 細節對比
2.1 多尺度霍夫直線 HoughLines
int getCrossScale_HoughL(cv::Mat srcImg, cv::Point2f& crossPoint, bool isShow)
{cv::Mat grayImg, binaryImg, grayImg2;cvtColor(srcImg, grayImg, COLOR_BGR2GRAY);//反色grayImg2 = 255 - grayImg;cv::threshold(grayImg2, binaryImg, 200, 255, THRESH_BINARY);vector<Vec2f> lines;//極坐標(r,theta)HoughLines(binaryImg, lines, 1, CV_PI / 180, 260, 0, 0);//累加器閾值threshold,越大線條越少,反之越多drawHoughLine(srcImg, lines,Scalar(0,0,255),1);//getLineCross(srcImg, lines, crossPoint, isShow);if (isShow){cv::imshow("灰度圖", grayImg);cv::imshow("反色", grayImg2);cv::imshow("二值化", binaryImg);cv::imshow("getCrossScale_HoughL", srcImg);cv::waitKey(0);destroyAllWindows();}return 0;
}
橫豎各找到3條線,
傾斜較為明顯,線段兩端呈放射狀;
再看中間交點 橫向占2個像素,豎向占3個像素
2.2 漸進概率霍夫線段 HoughLinesP
int getCrossScale_HoughLP(cv::Mat srcImg, cv::Point2f& crossPoint, bool isShow)
{cv::Mat grayImg, binaryImg, grayImg2;cvtColor(srcImg, grayImg, COLOR_BGR2GRAY);//反色grayImg2 = 255 - grayImg;cv::threshold(grayImg2, binaryImg, 200, 255, THRESH_BINARY);//利用漸進概率式霍夫變換提取 直線段vector<Vec4i> linesP;//每條線有四個參數,分別是選段兩端點坐標(x1,y1,x2,y2)HoughLinesP(binaryImg, linesP, 1, CV_PI / 180, 260, srcImg.cols * 0.6, 5); //兩個點連接最大距離10,CV_PI / 180表示1度對應的弧度for (size_t i = 0; i < linesP.size(); i++){line(srcImg, Point(linesP[i][0], linesP[i][1]), Point(linesP[i][2], linesP[i][3]), Scalar(255,0,0), 1);//測試坐標點//cout << "linesP1[" << i << "][0]" << linesP1[i][0] << "," << "linesP1[" << i << "][1]" << linesP1[i][1] << endl;}//getLineCross(srcImg, lines, crossPoint, isShow);if (isShow){cv::imshow("灰度圖", grayImg);cv::imshow("反色", grayImg2);cv::imshow("二值化", binaryImg);cv::imshow("getCrossScale_HoughLP", srcImg);cv::waitKey(0);destroyAllWindows();}return 0;
}
橫向找到3條線,豎向找到1條線;
橫向線段 3條線非常貼合,看似平行;(線段兩端沒有呈放射狀;)
再看中間交點 橫向占3個像素,豎向占1個像素
2.3 HoughLines 和 HoughLinesP 所求結果細節對比
對比兩函數參數,發現HoughLinesP 似乎比 HoughLines 有更多的約束條件;
HoughLinesP 多了最先線段長度minLineLength
,最小間隙maxLineGap
;
void HoughLines( InputArray image, OutputArray lines,double rho, double theta, int threshold,double srn = 0, double stn = 0,double min_theta = 0, double max_theta = CV_PI );
void HoughLinesP( InputArray image, OutputArray lines,double rho, double theta, int threshold,double minLineLength = 0, double maxLineGap = 0 );
- 在使用上的區別,HoughLinesP設定 最小那線段長度為 圖片高度的0.6倍;兩線之間最小間隙為5;
- 此外在使用上HoughLinesP,似乎更方便
HoughLines
, 每條線返回2個參數,(r,theta);HoughLinesP
,每條線返回4個參數,分別是選段兩端點坐標(x1,y1,x2,y2);
vector<Vec2f> lines;////每條線有2個參數,極坐標(r,theta)HoughLines(binaryImg, lines, 1, CV_PI / 180, 260, 0, 0);//累加器閾值threshold,越大線條越少,反之越多//利用漸進概率式霍夫變換提取 直線段vector<Vec4i> linesP;//每條線有四個參數,分別是選段兩端點坐標(x1,y1,x2,y2)HoughLinesP(binaryImg, linesP, 1, CV_PI / 180, 260, srcImg.cols * 0.6, 5); //兩個點連接最大距離10,CV_PI / 180表示1度對應的弧度
紅色HoughLines
:橫豎各找到3條線;線段兩端呈放射狀;
藍色HoughLinesP
:橫向找到3條線,豎向找到1條線;橫向線段 3條線非常貼合,看似平行,線段兩端沒有呈放射狀;
紅色HoughLines
: 再看中間交點 橫向占2個像素,豎向占3個像素;
藍色HoughLinesP
:再看中間交點 橫向占3個像素,豎向占1個像素
對比來看,HoughLinesP
效果更好一下;
2.4 為什么 HoughLinesP 直線兩端沒有呈放射狀態呢?直線總是平行嗎?
在觀察發現 HoughLinesP ,返回的直線vector linesP是 int型,而HoughLines 返回的直線是flaot類型;
難道是HoughLinesP 直線精度不高,稍微的歪斜,放射狀沒表現出來嗎?
vector<Vec2f> lines;////每條線有2個參數,極坐標(r,theta)HoughLines(binaryImg, lines, 1, CV_PI / 180, 260, 0, 0);//累加器閾值threshold,越大線條越少,反之越多//利用漸進概率式霍夫變換提取 直線段vector<Vec4i> linesP;//每條線有四個參數,分別是選段兩端點坐標(x1,y1,x2,y2)HoughLinesP(binaryImg, linesP, 1, CV_PI / 180, 260, srcImg.cols * 0.6, 5); //兩個點連接最大距離10,CV_PI / 180表示1度對應的弧度
將HoughLinesP的直線類型vector<Vec4i> linesP
;改為float類型vector<Vec4f> linesP
;
更換傾斜的十字測試
發現,線集是平行的,沒有放射狀;
linesP.size() = 5
[262, 390, 319, 28]
[107, 215, 475, 215]
[108, 216, 474, 216]
[260, 391, 318, 23]
[107, 214, 474, 214]
更換傾斜的十字測試
發現,線集是平行的,還是沒有放射狀;
linesP.size() = 16
[25, 193, 392, 206]
[203, 365, 215, 20]
[196, 363, 208, 17]
[200, 365, 212, 20]
[206, 365, 218, 14]
[202, 365, 214, 19]
[204, 365, 217, 15]
[26, 192, 397, 205]
[26, 195, 387, 207]
[198, 364, 210, 18]
[26, 190, 398, 203]
[27, 7, 286, 48]
[26, 196, 385, 209]
[62, 190, 398, 202]
[197, 365, 208, 51]
[22, 198, 385, 211]
2.5 HoughLines 直線角度,輸出有順序?
一依此繪制出HoughLines
的每一條直線,發現還是有規律的;
如下圖所示:
第一條直線 傾斜 9度,豎向;(OpenCV里HoughLines的角度應該是與Y軸的夾角)
第二條直線 傾斜90度,橫向;
。
。
。
依次豎向,橫向,豎向,橫向。。。循環輸出所有直線 (輸出順序似乎是碰巧)
lines 0[319, 0.15708] 角度 9
lines 1[215, 1.5708] 角度 90
lines 2[315, 0.139626] 角度 8
lines 3[221, 1.55334] 角度 89
lines 4[323, 0.174533] 角度 10
lines 5[211, 1.58825] 角度 91
輸出順序似乎是碰巧,再換一個圖像試試;
這次就么那么規律了,橫向和豎向的線 “群居”;
lines 0[192, 1.6057] 角度 92
lines 1[196, 1.58825] 角度 91
lines 2[189, 1.62316] 角度 93
lines 3[209, 0.0174533] 角度 1
lines 4[185, 1.64061] 角度 94
lines 5[206, 0] 角度 0
lines 6[215, 0.0349066] 角度 2
lines 7[213, 0.0349066] 角度 2
lines 8[216, 0.0523599] 角度 3
lines 9[218, 0.0523599] 角度 3
lines 10[219, 0.0698132] 角度 4
lines 11[222, 0.0698132] 角度 4
lines 12[224, 0.0872665] 角度 5
lines 13[199, 1.5708] 角度 90
3 獲取十字交點坐標
前面只是獲得了 橫豎直線,視覺上交點找到了,但交點坐標是多少?還不知道
思路:橫豎方向各選取一條直線,然后求兩線交點坐標;
兩線交點坐標函數如下,為了方便,每條線各取兩個點帶入;
bool get2linesIntersectionPoint3(cv::Point2f pointA, cv::Point2f pointB, cv::Point2f pointC, cv::Point2f pointD, cv::Point2f& crossPoint);
3.1 找到兩直線
3.1.1 獲得 HoughLines 橫豎兩條線
注意 : HoughLines 水平線90度,豎直線0度;
OpenCV里HoughLines的角度應該是與Y軸的夾角;
思路:
輸入HoughLines直線lines,選取橫豎兩條直線,在選取的直線上各取兩個點,由ptsOnLine帶出來;
bool getPointOn2Line(Mat& img, vector<Vec2f> lines, vector<cv::Point2f>& ptsOnLine,bool isShow)
{vector<Vec2f> linesHV;float rho, theta;bool linesH = false, linesV = false;//找水平、垂直,兩條線 //水平線90度,豎直線0度;OpenCV里HoughLines的角度應該是與Y軸的夾角for (size_t i = 0; i < lines.size(); i++){theta = lines[i][1]; //直線過坐標原點垂線與x軸夾角if (!linesH && (theta > CV_PI / 2 - 0.2 && theta < CV_PI / 2 + 0.2))//橫線theta接近π/2,±δ,{linesHV.push_back(lines[i]);linesH = true;}else if (!linesV && ((theta > -0.2 && theta < 0.2) || theta > CV_PI - 0.2 && theta < CV_PI + 0.2))//豎線theta接近π或0,±δ{linesHV.push_back(lines[i]);linesV = true;}//橫線豎線都找到了,跳出循環if (linesH && linesV){break;}}//求兩條直線上的四個點if (linesHV.size() == 2){drawHoughLine(img, linesHV, ptsOnLine, Scalar(0, 0, 255), 1, isShow);}else{cout << "沒找到兩條直線" << endl;return false;}cv::Rect rect;return true;
}
交點坐標為(248.8,215.0)
3.1.2 獲得HoughLinesP 橫豎兩條線
思路:
輸入HoughLinesP直線linesP,選取橫豎兩條直線,在選取的直線上各取兩個點,由ptsOnLine帶出來
注意:
因為HoughLinesP線集是平行的,當同一方向有多條線時,隨便選一條會導致中心不準,
當同一方向有多條線時,端點坐標取均值;
//輸入HoughLinesP直線linesP,選取橫豎兩條直線,在選取的直線上各取兩個點,由ptsOnLine帶出來
bool getPointOn2LineP(Mat& img, vector<Vec4i> linesP, vector<cv::Point2f>& ptsOnLine, bool isShow)
{float rho, theta;Point2f pt1, pt2;double angle;Point2f pts1_H(0,0), pts2_H(0, 0), pts1_V(0, 0), pts2_V(0, 0);int h = 0, v = 0;//找水平、垂直,兩條線//因為HoughLinesP線集是平行的,當同一方向有多條線時,隨便選一條會導致中心不準,//當同一方向有多條線端點坐標取均值;for (size_t i = 0; i < linesP.size(); i++){pt1.x = linesP[i][0];pt1.y = linesP[i][1];pt2.x = linesP[i][2];pt2.y = linesP[i][3];//linesP的每條直線與y軸的夾角angle = getLineAngle(pt1, pt2, Point2f(0,0), Point2f(0,img.rows));//水平線90度,豎直線0度;;;直線與Y軸的夾角(與HoughLines的夾角保持一致)if (angle > 70 && angle < 110)//如果橫線90°±20°{pts1_H.x += pt1.x;pts1_H.y += pt1.y;pts2_H.x += pt2.x;pts2_H.y += pt2.y;h++;}else if (angle > -20 && angle < 20)//如果豎線0°±20°{pts1_V.x += pt1.x;pts1_V.y += pt1.y;pts2_V.x += pt2.x;pts2_V.y += pt2.y;v++;}}pts1_H.x /= h;pts1_H.y /= h;pts2_H.x /= h;pts2_H.y /= h;pts1_V.x /= v;pts1_V.y /= v;pts2_V.x /= v;pts2_V.y /= v;ptsOnLine.push_back(pts1_H);ptsOnLine.push_back(pts2_H);ptsOnLine.push_back(pts1_V);ptsOnLine.push_back(pts2_V);line(img, pts1_H, pts2_H, Scalar(255, 0, 0), 1);line(img, pts1_V, pts2_V, Scalar(255, 0, 0), 1);if(isShow){cv::imshow("img", img);cv::waitKey(0);destroyAllWindows();}return true;
}
交點坐標為(249.1,215.0)
3.2 計算交點坐標原理 及實現
////****************************************************************************************
// 求二條直線的交點的公式
// 有如下方程 (x-x1)/(y-y1) = (x2-x1)/(y2-y1) ==> a1*x+b1*y=c1
// (x-x3)/(y-y3) = (x4-x3)/(y4-y3) ==> a2*x+b2*y=c2
// 則交點為
// x= D1/D =| c1 b1| / | a1 b1 | y= D2/D= | a1 c1| / | a1 b1 | //當兩條線平行或重合時,分母為零
// | c2 b2| / | a2 b2 | | a2 c2| / | a2 b2 |
//
// 注:D是其次ax+by=0,的行列式,Di是把第i列換成等式右邊的列(常數),i是所求未知數所在的列,如x在第一列,D1
//
// a1= y2-y1
// b1= x1-x2
// c1= x1*y2-x2*y1,這里 a1*x+b1*y=c1,和Ax+By+C=0,的C是一個負號關系
// a2= y4-y3
// b2= x3-x4
// c2= x3*y4-x4*y3
//
////****************************************************************************************
////
////行列式法,x= D1/D, y= D2/D,求兩直線的交點, //適合任意情況(斜率存在,不存在)
bool get2linesIntersectionPoint(cv::Point2f pointA, cv::Point2f pointB, cv::Point2f pointC, cv::Point2f pointD, cv::Point2f& crossPoint)
{float a1 = pointB.y - pointA.y;float b1 = pointA.x - pointB.x;float c1 = pointA.x * pointB.y - pointB.x * pointA.y;//這里 a1*x+b1*y=c1,和Ax+By+C=0,的C是一個負號關系float a2 = pointD.y - pointC.y;float b2 = pointC.x - pointD.x;float c2 = pointC.x * pointD.y - pointD.x * pointC.y;float det = a1 * b2 - a2 * b1;// 直線平行:A1/A2=B1/B2≠C1/C2 (A2B2C2≠0); 重合:A1/A2=B1/B2=C1/C2(A2B2C2≠0)// 直線平行:A1B2=A2B1; 重合:A1B2=A2B1=A1C2if (det == 0) return false;//平行或重合//Now this is cross point of linescrossPoint.x = (b2 * c1 - b1 * c2) / det;//這里和公式法(ABC)也是負號關系crossPoint.y = (a1 * c2 - a2 * c1) / det;return true;
}
由上面兩種方式獲取的直線,分別帶入交點坐標函數,求得交點如圖:
紅色HoughLines
: 交點坐標為(248.8,215.0)
藍色HoughLinesP
:交點坐標為(249.1,215.0)
二者橫坐標,相差0.3像素,縱坐標相等,
該工業相機為2000萬像素,可見精度還是很高的
比較細節,看看哪個更準?
比較發現,由于刻度尺上端被黑色的記號筆涂抹記號,對直線檢測結果有一定影響
;
但比較細節,藍色受影響更小一些; 如果想要更加精準,ROI范圍可以再小一些,把記號筆涂抹部分去掉;
4 項目源碼
4.1 .h文件
#pragma once
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;//獲得黑十字刻度尺 交點坐標(轉換為在B2大ROI中的位置),顯示在srcImgBigROI上
int getCrossScale_HoughL(cv::Mat srcImg, cv::Point2f& crossPoint, bool isShow);
int getCrossScale_HoughLP(cv::Mat srcImg, cv::Point2f& crossPoint, bool isShow);bool getPointOn2Line( Mat& img, vector<Vec2f> lines, vector<cv::Point2f>& ptsOnLine, bool isShow);
bool getPointOn2LineP(Mat& img, vector<Vec4i> linesP, vector<cv::Point2f>& ptsOnLine, bool isShow);//返回直線上兩坐標點,isDraw設置是否繪制直線
void drawHoughLine(Mat& img, vector<Vec2f> lines, vector<cv::Point2f> &ptsOnLine, const Scalar& color, int thickness, bool isDraw);//要標記直線的圖像,檢測的直線數據bool get2linesIntersectionPoint(cv::Point2f pointA, cv::Point2f pointB, cv::Point2f pointC, cv::Point2f pointD, cv::Point2f& crossPoint);
//已知每條直線的兩個點求夾角,,返回角度
double getLineAngle(cv::Point2f pointA, cv::Point2f pointB, cv::Point2f pointC, cv::Point2f pointD);
4.2 .cpp文件
#include "getCross.h"int getCrossScale_HoughL(cv::Mat srcImg, cv::Point2f& crossPoint, bool isShow)
{cv::Mat grayImg, binaryImg, grayImg2;cvtColor(srcImg, grayImg, COLOR_BGR2GRAY);//反色grayImg2 = 255 - grayImg;cv::threshold(grayImg2, binaryImg, 200, 255, THRESH_BINARY);vector<Vec2f> lines;////每條線有2個參數,極坐標(r,theta)HoughLines(binaryImg, lines, 1, CV_PI / 180, 260, 0, 0);//累加器閾值threshold,越大線條越少,反之越多vector<cv::Point2f> ptsOnLine;//獲得兩直線getPointOn2Line(srcImg, lines, ptsOnLine, false);//獲得兩直線交點get2linesIntersectionPoint(ptsOnLine[0], ptsOnLine[1], ptsOnLine[2], ptsOnLine[3], crossPoint);if (isShow){//繪制交點坐標char buf[50];memset(buf, '\0', 50);sprintf_s(buf, "X = %.1f; Y = %.1f;", crossPoint.x, crossPoint.y);int font_face = FONT_HERSHEY_COMPLEX;double font_scale = 0.5;int thickness = 1;putText(srcImg, buf, Point(crossPoint.x + 20, crossPoint.y - 20), font_face, font_scale, Scalar(0, 0, 255), thickness, 8, 0);imshow("十字刻度尺 交點:", srcImg);cv::waitKey(0);destroyAllWindows();}return 0;
}int getCrossScale_HoughLP(cv::Mat srcImg, cv::Point2f& crossPoint, bool isShow)
{cv::Mat grayImg, binaryImg, grayImg2;cvtColor(srcImg, grayImg, COLOR_BGR2GRAY);//反色grayImg2 = 255 - grayImg;cv::threshold(grayImg2, binaryImg, 200, 255, THRESH_BINARY);//利用漸進概率式霍夫變換提取 直線段vector<Vec4i> linesP;//每條線有四個參數,分別是選段兩端點坐標(x1,y1,x2,y2)HoughLinesP(binaryImg, linesP, 1, CV_PI / 180, 260, srcImg.cols * 0.6, 5); //兩個點連接最大距離10,CV_PI / 180表示1度對應的弧度////繪制所有線段查看效果//cout << "linesP.size() = " << linesP.size() << endl;//for (size_t i = 0; i < linesP.size(); i++)//{// //linesP1[i][0]第i條線段的x坐標、linesP1[i][1]第i條線段的y坐標// line(srcImg, Point(linesP[i][0], linesP[i][1]), Point(linesP[i][2], linesP[i][3]), Scalar(255,0,0), 1);// cout << linesP[i]<< endl;//測試坐標點//}vector<cv::Point2f> ptsOnLine;//獲得兩直線getPointOn2LineP(srcImg, linesP, ptsOnLine, false);//獲得兩直線交點get2linesIntersectionPoint(ptsOnLine[0], ptsOnLine[1], ptsOnLine[2], ptsOnLine[3], crossPoint);if (isShow){//繪制交點坐標char buf[50];memset(buf, '\0', 50);sprintf_s(buf, "X = %.1f; Y = %.1f;", crossPoint.x, crossPoint.y);int font_face = FONT_HERSHEY_COMPLEX;double font_scale = 0.5;int thickness = 1;putText(srcImg, buf, Point(crossPoint.x + 20, crossPoint.y - 20), font_face, font_scale, Scalar(0, 0, 255), thickness, 8, 0);imshow("十字刻度尺 交點:", srcImg);cv::waitKey(0);destroyAllWindows();}return 0;
}//輸入HoughLines直線lines,選取橫豎兩條直線,在選取的直線上各取兩個點,由ptsOnLine帶出來
bool getPointOn2Line(Mat& img, vector<Vec2f> lines, vector<cv::Point2f>& ptsOnLine,bool isShow)
{vector<Vec2f> linesHV;float rho, theta;bool linesH = false, linesV = false;//找水平、垂直,兩條線 //水平線90度,豎直線0度;OpenCV里HoughLines的角度應該是與Y軸的夾角for (size_t i = 0; i < lines.size(); i++){theta = lines[i][1]; //直線過坐標原點垂線與x軸夾角if (!linesH && (theta > CV_PI / 2 - 0.2 && theta < CV_PI / 2 + 0.2))//橫線theta接近π/2,±δ,{linesHV.push_back(lines[i]);linesH = true;}else if (!linesV && ((theta > -0.2 && theta < 0.2) || theta > CV_PI - 0.2 && theta < CV_PI + 0.2))//豎線theta接近π或0,±δ{linesHV.push_back(lines[i]);linesV = true;}//橫線豎線都找到了,跳出循環if (linesH && linesV){break;}}//求兩條直線上的四個點if (linesHV.size() == 2){drawHoughLine(img, linesHV, ptsOnLine, Scalar(0, 0, 255), 1, isShow);}else{cout << "沒找到兩條直線" << endl;return false;}cv::Rect rect;return true;
}//輸入HoughLinesP直線linesP,選取橫豎兩條直線,在選取的直線上各取兩個點,由ptsOnLine帶出來
bool getPointOn2LineP(Mat& img, vector<Vec4i> linesP, vector<cv::Point2f>& ptsOnLine, bool isShow)
{float rho, theta;Point2f pt1, pt2;double angle;Point2f pts1_H(0,0), pts2_H(0, 0), pts1_V(0, 0), pts2_V(0, 0);int h = 0, v = 0;//找水平、垂直,兩條線//因為HoughLinesP線集是平行的,當同一方向有多條線時,隨便選一條會導致中心不準,//當同一方向有多條線端點坐標取均值;for (size_t i = 0; i < linesP.size(); i++){pt1.x = linesP[i][0];pt1.y = linesP[i][1];pt2.x = linesP[i][2];pt2.y = linesP[i][3];//linesP的每條直線與y軸的夾角angle = getLineAngle(pt1, pt2, Point2f(0,0), Point2f(0,img.rows));//水平線90度,豎直線0度;;;直線與Y軸的夾角(與HoughLines的夾角保持一致)if (angle > 70 && angle < 110)//如果橫線90°±20°{pts1_H.x += pt1.x;pts1_H.y += pt1.y;pts2_H.x += pt2.x;pts2_H.y += pt2.y;h++;}else if (angle > -20 && angle < 20)//如果豎線0°±20°{pts1_V.x += pt1.x;pts1_V.y += pt1.y;pts2_V.x += pt2.x;pts2_V.y += pt2.y;v++;}}pts1_H.x /= h;pts1_H.y /= h;pts2_H.x /= h;pts2_H.y /= h;pts1_V.x /= v;pts1_V.y /= v;pts2_V.x /= v;pts2_V.y /= v;ptsOnLine.push_back(pts1_H);ptsOnLine.push_back(pts2_H);ptsOnLine.push_back(pts1_V);ptsOnLine.push_back(pts2_V);line(img, pts1_H, pts2_H, Scalar(255, 0, 0), 1);line(img, pts1_V, pts2_V, Scalar(255, 0, 0), 1);if(isShow){cv::imshow("img", img);cv::waitKey(0);destroyAllWindows();}return true;
}////****************************************************************************************
// 求二條直線的交點的公式
// 有如下方程 (x-x1)/(y-y1) = (x2-x1)/(y2-y1) ==> a1*x+b1*y=c1
// (x-x3)/(y-y3) = (x4-x3)/(y4-y3) ==> a2*x+b2*y=c2
// 則交點為
// x= D1/D =| c1 b1| / | a1 b1 | y= D2/D= | a1 c1| / | a1 b1 | //當兩條線平行或重合時,分母為零
// | c2 b2| / | a2 b2 | | a2 c2| / | a2 b2 |
//
// 注:D是其次ax+by=0,的行列式,Di是把第i列換成等式右邊的列(常數),i是所求未知數所在的列,如x在第一列,D1
//
// a1= y2-y1
// b1= x1-x2
// c1= x1*y2-x2*y1,這里 a1*x+b1*y=c1,和Ax+By+C=0,的C是一個負號關系
// a2= y4-y3
// b2= x3-x4
// c2= x3*y4-x4*y3
//
////****************************************************************************************
////
////行列式法,x= D1/D, y= D2/D,求兩直線的交點, //適合任意情況(斜率存在,不存在)
bool get2linesIntersectionPoint(cv::Point2f pointA, cv::Point2f pointB, cv::Point2f pointC, cv::Point2f pointD, cv::Point2f& crossPoint)
{float a1 = pointB.y - pointA.y;float b1 = pointA.x - pointB.x;float c1 = pointA.x * pointB.y - pointB.x * pointA.y;//這里 a1*x+b1*y=c1,和Ax+By+C=0,的C是一個負號關系float a2 = pointD.y - pointC.y;float b2 = pointC.x - pointD.x;float c2 = pointC.x * pointD.y - pointD.x * pointC.y;float det = a1 * b2 - a2 * b1;// 直線平行:A1/A2=B1/B2≠C1/C2 (A2B2C2≠0); 重合:A1/A2=B1/B2=C1/C2(A2B2C2≠0)// 直線平行:A1B2=A2B1; 重合:A1B2=A2B1=A1C2if (det == 0) return false;//平行或重合//Now this is cross point of linescrossPoint.x = (b2 * c1 - b1 * c2) / det;//這里和公式法(ABC)也是負號關系crossPoint.y = (a1 * c2 - a2 * c1) / det;return true;
}//*********************************************************
//求兩直線的夾角//已知兩點坐標求向量:A(a1, b1), B(a2, b2, ), 則向量AB為:B點坐標減A點坐標,即:向量AB = (a2-a1, b2-b1);
//已知兩向量坐標,求兩向量夾角:設兩個向量分別為a = (x1,y1), b = (x2, y2),其夾角為α,因為ab = |a||b| cosα,所以cosα = ab/|a||b|= (x1x2+y1y2) / (根號(x1^2 + y1^2)根號(x2^2 + y2^2));
//兩向量內積:已知兩向量a = [a1, a2, …, an]和b = [b1, b2, …, bn]的點積定義為:內積就是點積 a·b=a1b1+a2b2+……+anbn;
//向量的模,即向量的長度,設向量a = (x, y),則向量a的模 = 根號(x方 + y方)//夾角為α = arccos(∑(xiyi) / sqrt((∑(xixi)∑(yiyi)))
//cosα = 兩個向量的內積 / 向量的模(“長度”)的乘積//*********************************************************//已知每條直線的兩個點求夾角
double getLineAngle(cv::Point2f pointA, cv::Point2f pointB, cv::Point2f pointC, cv::Point2f pointD)
{//向量AB,CDauto v1 = pointB - pointA;auto v2 = pointD - pointC;//向量AB,CD的模double n1 = cv::norm(v1);double n2 = cv::norm(v2);//cosα = ab/|a||b|double cosv = (v1.x * v2.x + v1.y * v2.y) / n1 / n2;double angle_rad = acos(cosv);//弧度轉角度return angle_rad * 180 / CV_PI;
}//返回直線上兩坐標點,有時只需要兩個點的坐標,并不需要繪制顯示直線
void drawHoughLine(Mat& img, vector<Vec2f> lines, vector<cv::Point2f> &ptsOnLine ,const Scalar& color, int thickness,bool isShow )//要標記直線的圖像,檢測的直線數據
{double length = max(img.rows, img.cols); //圖像高寬的最大值Point2f pt1, pt2;float rho, theta;double a, b, x0, y0;for (size_t i = 0; i < lines.size(); i++){rho = lines[i][0]; //直線距離坐標原點的距離theta = lines[i][1]; //直線過坐標原點垂線與x軸夾角a = cos(theta); //夾角的余弦值b = sin(theta); //夾角的正弦值//x = r*cos(θ),y = r*sin(θ)x0 = a * rho, y0 = b * rho; //直線與過坐標原點的垂線的交點//計算直線上的一點pt1.x = x0 + length * (-b);pt1.y = y0 + length * (a);//計算直線上另一點pt2.x = x0 - length * (-b);pt2.y = y0 - length * (a);////若想獲得整數點,可用cvRound()四舍五入;//pt1.x = cvRound(x0 + length * (-b));//返回跟參數最接近的整數值,即四舍五入;//pt1.y = cvRound(y0 + length * (a));//pt2.x = cvRound(x0 - length * (-b));//pt2.y = cvRound(y0 - length * (a));//兩點繪制一條直線line(img, pt1, pt2, color, thickness);if (isShow){cout << "lines " << i << lines[i] << "\t角度 " << lines[i][1] * 180 / CV_PI << endl;cv::imshow("img", img);cv::waitKey(0);destroyAllWindows();}ptsOnLine.push_back(pt1);ptsOnLine.push_back(pt2); }
}
4.3 main文件
#include "getCross.h"void main()
{char imgPath[] = "D:\\C_test\\images\\8.bmp";cv::Mat srcImg = imread(imgPath);cv::Point2f crossPoint;//getCrossScale_HoughL(srcImg, crossPoint, true);getCrossScale_HoughLP(srcImg, crossPoint, true);
}
5 將 以上兩種封裝成一個函數——新增方法切換,轉換大圖坐標等功能
獲得黑十字刻度尺交點坐標,有HoughLP、HoughL兩種方法可選用,(ptsROIltop可將交點轉換為大圖中的位置,顯示在ROI所在的圖像上)
bool getLineCrossPoint2f(cv::Mat srcImg, Mat ImgDraw, string getLineMethod , Point ptsROIltop, cv::Point2f& crossPoint, bool isShow)
新增參數解釋:
Mat ImgDraw
,傳入用來繪制線條等信息的圖像(有時并不需要將線條繪制在處理的圖像上);string getLineMethod
,有HoughLP、HoughL兩種方法可選用;Point ptsROIltop
是ROI左上角坐標,用于將交點轉換為大圖中的位置,顯示在ROI所在的圖像上;
// 獲得黑十字刻度尺交點坐標,有HoughLP、HoughL兩種方法可選用,(ptsROIltop可將交點轉換為大圖中的位置,顯示在ROI所在的圖像上)
bool getLine::getLineCrossPoint2f(cv::Mat srcImg, Mat ImgDraw, string getLineMethod , Point ptsROIltop, cv::Point2f& crossPoint, bool isShow)
{cv::Mat grayImg, binaryImg, grayImg2;cvtColor(srcImg, grayImg, COLOR_BGR2GRAY);//反色grayImg2 = 255 - grayImg;cv::threshold(grayImg2, binaryImg, 200, 255, THRESH_BINARY);vector<cv::Point2f> ptsOnLine;//獲取直線上的兩點if (getLineMethod == "HoughLP"){//利用漸進概率式霍夫變換提取 直線段vector<Vec4i> linesP;//每條線有四個參數,分別是選段兩端點坐標(x1,y1,x2,y2)HoughLinesP(binaryImg, linesP, 1, CV_PI / 180, 260, srcImg.cols * 0.6, 5); //兩個點連接最大距離10,CV_PI / 180表示1度對應的弧度////繪制所有線段查看效果//drawHoughLineP(srcImg, linesP, Scalar(0, 0, 255), 1, isShow);//獲得兩相交直線get2CrossHoughLP(srcImg, linesP,ptsOnLine, false);}else if (getLineMethod == "HoughL"){vector<Vec2f> lines;////每條線有2個參數,極坐標(r,theta)HoughLines(binaryImg, lines, 1, CV_PI / 180, 260, 0, 0);//累加器閾值threshold,越大線條越少,反之越多//繪制所有直線查看效果//drawHoughLine(srcImg, lines,ptsOnLine, Scalar(0, 0, 255), 1, isShow);vector<cv::Point2f> ptsOnLine;//獲得兩相交直線,get2CrossHoughL(srcImg, lines,ptsOnLine, false);}if (ptsOnLine.size() == 4){//將直線坐標點轉換為大圖中的位置,ptsROIltop是ROI左上角坐標, 直線顯示在srcImgBigROI上//當ptsROIltop為(0,0)時,繪制在ROI圖上;當為ROI左上角坐標時,直線繪制在ROI所在的大圖上;ptsOnLine[0].x += ptsROIltop.x; ptsOnLine[0].y += ptsROIltop.y;ptsOnLine[1].x += ptsROIltop.x; ptsOnLine[1].y += ptsROIltop.y;ptsOnLine[2].x += ptsROIltop.x; ptsOnLine[2].y += ptsROIltop.y;ptsOnLine[3].x += ptsROIltop.x; ptsOnLine[3].y += ptsROIltop.y;//獲得兩直線交點get2linesIntersectionPoint3(ptsOnLine[0], ptsOnLine[1], ptsOnLine[2], ptsOnLine[3], crossPoint);}else{cout << "沒找到兩條直線" << endl;return false;}if (isShow){ //繪制兩交線line(ImgDraw, ptsOnLine[0], ptsOnLine[1], Scalar(255, 0, 0), 1);line(ImgDraw, ptsOnLine[2], ptsOnLine[3], Scalar(255, 0, 0), 1);//繪制交點坐標char buf[50];memset(buf, '\0', 50);sprintf_s(buf, "X = %.1f; Y = %.1f;", crossPoint.x, crossPoint.y);int font_face = FONT_HERSHEY_COMPLEX;double font_scale = 0.5;int thickness = 1;putText(ImgDraw, buf, Point(crossPoint.x + 20, crossPoint.y - 20), font_face, font_scale, Scalar(0, 0, 255), thickness, 8, 0);imshow("十字刻度尺 交點:", ImgDraw);cv::waitKey(0);destroyAllWindows();}return true;
}
繪制兩種霍夫直線的函數
//返回每條直線上兩坐標點
void getLine::drawHoughLine(Mat& img, vector<Vec2f> lines, vector<cv::Point2f>& ptsOnLine, const Scalar& color, int thickness, bool isShow)//要標記直線的圖像,檢測的直線數據
{double length = max(img.rows, img.cols); //圖像高寬的最大值Point2f pt1, pt2;float rho, theta;double a, b, x0, y0;for (size_t i = 0; i < lines.size(); i++){rho = lines[i][0]; //直線距離坐標原點的距離theta = lines[i][1]; //直線過坐標原點垂線與x軸夾角a = cos(theta); //夾角的余弦值b = sin(theta); //夾角的正弦值//x = r*cos(θ),y = r*sin(θ)x0 = a * rho, y0 = b * rho; //直線與過坐標原點的垂線的交點//計算直線上的一點pt1.x = x0 + length * (-b);pt1.y = y0 + length * (a) ;//計算直線上另一點pt2.x = x0 - length * (-b);pt2.y = y0 - length * (a);////若想獲得整數點,可用cvRound()四舍五入;//pt1.x = cvRound(x0 + length * (-b));//返回跟參數最接近的整數值,即四舍五入;//pt1.y = cvRound(y0 + length * (a));//pt2.x = cvRound(x0 - length * (-b));//pt2.y = cvRound(y0 - length * (a));//實踐發現://V方向,y軸向上超出img約 -img.rows,H方向,x軸向左超出img約 -img.cols,//為了將繪制的直線 限制在img范圍內,可將V方向,y軸上端 +img.rows,H方向,x軸左 +img.cols //這樣實際是不準的,因有傾斜,最準的方法用直線表達式,給定x,y,獲取指定位置坐標;//繪制的直線超出img范圍,以后在修改if (isShow){//兩點繪制一條直線line(img, pt1, pt2, color, thickness);cout << "lines " << i << lines[i] << "\t角度 " << lines[i][1] * 180 / CV_PI << endl;cv::imshow("img", img);cv::waitKey(0);destroyAllWindows();}ptsOnLine.push_back(pt1);ptsOnLine.push_back(pt2);}
}void getLine::drawHoughLineP(Mat& img, vector<Vec4i> linesP, const Scalar& color, int thickness, bool isShow)//要標記直線的圖像,檢測的直線數據
{cout << "linesP.size() = " << linesP.size() << endl;for (size_t i = 0; i < linesP.size(); i++){//linesP1[i][0]第i條線段的x坐標、linesP1[i][1]第i條線段的y坐標line(img, Point(linesP[i][0], linesP[i][1]), Point(linesP[i][2], linesP[i][3]), color, thickness);cout << linesP[i]<< endl;//測試坐標點}if (isShow){ cv::imshow("img", img);cv::waitKey(0);destroyAllWindows();}
}
下面的三個函數,前面已經介紹過,函數內容沒變,只是名字改了;
//行列式法,x= D1/D, y= D2/D,求兩直線的交點, //適合任意情況(斜率存在,不存在)
bool get2linesIntersectionPoint3(cv::Point2f pointA, cv::Point2f pointB, cv::Point2f pointC, cv::Point2f pointD, cv::Point2f& crossPoint);
//輸入HoughLines直線lines,選取橫豎兩條直線,在選取的直線上各取兩個點,由ptsOnLine帶出來;
bool get2CrossHoughL( Mat& img, vector<Vec2f> lines, vector<cv::Point2f>& ptsOnLine, bool isShow);
//輸入HoughLinesP直線linesP,選取橫豎兩條直線,在選取的直線上各取兩個點,由ptsOnLine帶出來;
bool get2CrossHoughLP(Mat& img, vector<Vec4i> linesP, vector<cv::Point2f>& ptsOnLine, bool isShow);