我們如何在圖像中快速識別出其中的圓和直線?一個非常有效的方法就是霍夫變換,它是圖像中識別各種幾何形狀的基本算法之一。
霍夫線變換
霍夫線變換是一種在圖像中尋找直線的方法。OpenCV中支持三種霍夫線變換,分別是標准霍夫線變換、多尺度霍夫線變換、累計概率霍夫線變換。
在OpenCV中可以調用函數HoughLines來調用標准霍夫線變換和多尺度霍夫線變換。HoughLinesP函數用於調用累積概率霍夫線變換。
我們都知道,二維坐標軸上表示一條直線的方程式y = a*x + b,我們想求出一條直線就得想方設法求出其中的a和b的值。如果用極坐標來表示就是
theta就是直線與水平線所成的角度,而rho就是圓的半徑(也可以理解為原點到直線的距離),同樣地,這兩個參數也是表征一條直線的重要參數,確定他們倆了,也就確定一條直線了。正如下圖所示。
在OpenCV里,我們只需調用HoughLines就是可以得到表征一條直線的這兩個參數值!
HoughLines用法
#include <iostream>
#include <opencv2\opencv.hpp>
#include <opencv2\imgproc\imgproc.hpp>
using namespace cv;
using namespace std;
int main()
{
Mat srcImage = imread("4.jpg");
imshow("Src Pic", srcImage);
Mat midImage, dstImage;
//邊緣檢測
Canny(srcImage, midImage, 50, 200, 3);
//灰度化
cvtColor(midImage, dstImage, CV_GRAY2BGR);
// 定義矢量結構存放檢測出來的直線
vector<Vec2f> lines;
//通過這個函數,我們就可以得到檢測出來的直線集合了
HoughLines(midImage, lines, 1, CV_PI / 180, 150, 0, 0);
//這里注意第五個參數,表示閾值,閾值越大,表明檢測的越精准,速度越快,得到的直線越少(得到的直線都是很有把握的直線)
//這里得到的lines是包含rho和theta的,而不包括直線上的點,所以下面需要根據得到的rho和theta來建立一條直線
//依次畫出每條線段
for (size_t i = 0; i < lines.size(); i++)
{
float rho = lines[i][0]; //就是圓的半徑r
float theta = lines[i][1]; //就是直線的角度
Point pt1, pt2;
double a = cos(theta), b = sin(theta);
double x0 = a*rho, y0 = b*rho;
pt1.x = cvRound(x0 + 1000 * (-b));
pt1.y = cvRound(y0 + 1000*(a));
pt2.x = cvRound(x0 - 1000*(-b));
pt2.y = cvRound(y0 - 1000 * (a));
line(dstImage, pt1, pt2, Scalar(55, 100, 195), 1, LINE_AA); //Scalar函數用於調節線段顏色,就是你想檢測到的線段顯示的是什么顏色
imshow("邊緣檢測后的圖", midImage);
imshow("最終效果圖", dstImage);
}
waitKey();
return 0;
}
原圖
閾值我設為250,看看直線檢測的效果。你會發現,怎么圖中一些很明顯的的直線都沒檢測出來啊?原因是,我們閾值寫的有點高了,只有那些有足夠的把握認為是直線的直線才可能檢測出來。
然后我把閾值改為150,直線檢測效果就變成這樣子了。顯然多了很多直線,這是我們把我們的要求降低了,把那些“可能是直線”的直線都當做是直線了。所以,閾值的選擇很重要,就看你是要精確查找還是模糊查找了。
后來我又加了一句打印進去,想看看角度的單位是什么
cout << "line " << i << ": " << "rho:" << rho << " theta:" << theta << endl;
可以看到,角度theta用的單位不是我們所說的度數(70度、80度),而是數學上的π/2,π/3。
要想轉為我們所說的度數,自己寫個轉換吧
float angle = theta / CV_PI * 180;
可以看出,轉換后的角度范圍就是我們想要的度數!值得注意的是,rho表示離坐標原點(就是圖片左上角的點)的距離,theta是直線的旋轉角度(0度表示垂直線,90度表示水平線)。
HoughLinesP用法
此函數在HoughLines的基礎上在末尾加了一個代表Probabilistic(概率)的P,表明使用的是累計概率變換。
#include <iostream>
#include <opencv2\opencv.hpp>
#include <opencv2\imgproc\imgproc.hpp>
using namespace cv;
using namespace std;
int main()
{
Mat srcImage = imread("2.jpg");
imshow("Src Pic", srcImage);
Mat midImage, dstImage;
Canny(srcImage, midImage, 50, 200, 3);
cvtColor(midImage, dstImage, CV_GRAY2BGR);
vector<Vec4i> lines;
//與HoughLines不同的是,HoughLinesP得到lines的是含有直線上點的坐標的,所以下面進行划線時就不再需要自己求出兩個點來確定唯一的直線了
HoughLinesP(midImage, lines, 1, CV_PI / 180, 80, 50, 10);//注意第五個參數,為閾值
//依次畫出每條線段
for (size_t i = 0; i < lines.size(); i++)
{
Vec4i l = lines[i];
line(dstImage, Point(l[0], l[1]), Point(l[2], l[3]), Scalar(186, 88, 255), 1, LINE_AA); //Scalar函數用於調節線段顏色
imshow("邊緣檢測后的圖", midImage);
imshow("最終效果圖", dstImage);
}
waitKey();
return 0;
}
貌似效果還不錯。
霍夫圓變換
剛剛的霍夫變換是檢測直線的,如果我們想檢測圓形,那該怎么辦?那就用霍夫圓變換!用法也大同小異。
#include <iostream>
#include <opencv2\opencv.hpp>
#include <opencv2\imgproc\imgproc.hpp>
using namespace cv;
using namespace std;
int main()
{
Mat srcImage = imread("test5.jpg");
Mat midImage, dstImage;//臨時變量和目標圖的定義
imshow("【原始圖】", srcImage);
//【3】轉為灰度圖,進行圖像平滑
cvtColor(srcImage, midImage, CV_BGR2GRAY);//轉化邊緣檢測后的圖為灰度圖
GaussianBlur(midImage, midImage, Size(9, 9), 2, 2);
//【4】進行霍夫圓變換
vector<Vec3f> circles;
HoughCircles(midImage, circles, CV_HOUGH_GRADIENT, 1.5, 10, 200, 150, 0, 0); //注意第七的參數為閾值,可以自行調整,值越大,檢測的圓更精准
//【5】依次在圖中繪制出圓
for (size_t i = 0; i < circles.size(); i++)
{
Point center(cvRound(circles[i][0]), cvRound(circles[i][1]));
int radius = cvRound(circles[i][2]);
//繪制圓心
circle(srcImage, center, 3, Scalar(0, 255, 0), -1, 8, 0);
//繪制圓輪廓
circle(srcImage, center, radius, Scalar(155, 50, 255), 3, 8, 0);
}
//【6】顯示效果圖
imshow("【效果圖】", srcImage);
waitKey(0);
return 0;
}
可以看到,有一些圓沒有檢測出來,同時還有一些不是圓形的確有誤以為是圓形了,說明閾值選擇不是很妥當。
另外提一點,霍夫圓變換的檢測速度真的慢,顯然進行圓檢測的計算量還真不少!