C語(yǔ)言實(shí)現(xiàn)opencv提取直線、輪廓及ROI實(shí)例詳解
一、Canny檢測(cè)輪廓
在上一篇文章中有提到sobel邊緣檢測(cè),并重寫(xiě)了soble的C++代碼讓其與matlab中算法效果一致,而soble邊緣檢測(cè)是基于單一閾值的,我們不能兼顧到低閾值的豐富邊緣和高閾值時(shí)的邊緣缺失這兩個(gè)問(wèn)題。而canny算子則很好的彌補(bǔ)了這一不足,從目前看來(lái),canny邊緣檢測(cè)在做圖像輪廓提取方面是最優(yōu)秀的邊緣檢測(cè)算法。
canny邊緣檢測(cè)采用雙閾值值法,高閾值用來(lái)檢測(cè)圖像中重要的、顯著的線條、輪廓等,而低閾值用來(lái)保證不丟失細(xì)節(jié)部分,低閾值檢測(cè)出來(lái)的邊緣更豐富,但是很多邊緣并不是我們關(guān)心的。最后采用一種查找算法,將低閾值中與高閾值的邊緣有重疊的線條保留,其他的線條都刪除。
本篇文章中不對(duì)canny的算法原理作進(jìn)一步說(shuō)明,稍后會(huì)在圖像處理算法相關(guān)的文章中詳細(xì)介紹。
下面我們用OpenCV中的Canny函數(shù)來(lái)檢測(cè)圖像邊緣
int main()
{
Mat I=imread("../cat.png");
cvtColor(I,I,CV_BGR2GRAY);
Mat contours;
Canny(I,contours,125,350);
threshold(contours,contours,128,255,THRESH_BINARY);
namedWindow("Canny");
imshow("Canny",contours);
waitKey();
return 0;
}
顯示效果如下:

二、直線檢測(cè)
用到的是霍夫變換檢測(cè)直線的算法
直線在圖像中出現(xiàn)的頻率非常之高,而直線作為圖像的特征對(duì)于基本內(nèi)容的圖像分析有著很重要的作用,本文通過(guò)OpenCV中的hough變換來(lái)檢測(cè)圖像中的線條。
我們先看最基本的Hough變換函數(shù)HoughLines,它的原型如下:
void HoughLines(InputArray image, OutputArray lines, double rho, double theta, int threshold, double srn=0, double stn=0 );
它的輸入是一個(gè)二值的輪廓圖像,往往是邊緣檢測(cè)得到的結(jié)果圖像;它的輸出是一個(gè)包含多個(gè)Vec2f點(diǎn)的數(shù)組,數(shù)組中的每個(gè)元素是一個(gè)二元浮點(diǎn)數(shù)據(jù)對(duì)<rou,theta>,rou代表直線離坐標(biāo)原點(diǎn)的距離,theta代表角度。第3和第4個(gè)參數(shù)代表步長(zhǎng),因?yàn)镠ough變換實(shí)際上是一個(gè)窮舉的算法,rho表示距離的步長(zhǎng),theta代表角度的步長(zhǎng)。第5個(gè)參數(shù)是一個(gè)閾值設(shè)置直接的最低投票個(gè)數(shù),知道Hough原理的,這個(gè)參數(shù)應(yīng)該很容易理解。
從這個(gè)函數(shù)的輸出結(jié)果我們可以看出,得到的直線并沒(méi)有指定在圖像中的開(kāi)始點(diǎn)與結(jié)束點(diǎn),需要我們自己去計(jì)算,如果我們想把直接顯示在圖像中就會(huì)比較麻煩,而且會(huì)有很多角度接近的直線,其實(shí)它們是重復(fù)的,為了解決上面這些問(wèn)題,OpenCV又提供了一個(gè)函數(shù)HoughLinesP()。它的輸出是一個(gè)Vector of Vec4i。Vector每一個(gè)元素代表一條直線,是由一個(gè)4元浮點(diǎn)數(shù)組構(gòu)成,前兩個(gè)點(diǎn)一組,后兩個(gè)點(diǎn)一組,代表了在圖像中直線的起始和結(jié)束點(diǎn)。
void HoughLinesP(InputArray image, OutputArray lines, double rho, double theta,int threshold, double minLineLength=0, double maxLineGap=0 );
解釋一下最后兩個(gè)參數(shù),minLineLength指定了檢測(cè)直線中的最小寬度,如果低于最小寬度則舍棄掉,maxLineGap指定通過(guò)同一點(diǎn)的直線,如果距離小于maxLineGap就會(huì)進(jìn)行合并。
下面是一個(gè)用HoughLinesP檢測(cè)直線的例子:
int main()
{
Mat image=imread("../car.png");
Mat I;
cvtColor(image,I,CV_BGR2GRAY);
Mat contours;
Canny(I,contours,125,350);
threshold(contours,contours,128,255,THRESH_BINARY);
vector<Vec4i> lines;
// 檢測(cè)直線,最小投票為90,線條不短于50,間隙不小于10
HoughLinesP(contours,lines,1,CV_PI/180,80,50,10);
drawDetectLines(image,lines,Scalar(0,255,0));
namedWindow("Lines");
imshow("Lines",image);
waitKey();
return 0;
}
上面程序?qū)z測(cè)到的線條保存在lines變量?jī)?nèi),我們需要進(jìn)一步將它們畫(huà)在圖像上:
void drawDetectLines(Mat& image,const vector<Vec4i>& lines,Scalar & color)
{
// 將檢測(cè)到的直線在圖上畫(huà)出來(lái)
vector<Vec4i>::const_iterator it=lines.begin();
while(it!=lines.end())
{
Point pt1((*it)[0],(*it)[1]);
Point pt2((*it)[2],(*it)[3]);
line(image,pt1,pt2,color,2); // 線條寬度設(shè)置為2
++it;
}
}

實(shí)際上Hough變換可以檢測(cè)很多固定的形狀,比如:圓、正方形等。它們的原理基本相同,都是構(gòu)造一個(gè)投票矩陣。OpenCV里提供了檢測(cè)圓的函數(shù)HoughCircles,它的輸出是一個(gè)Vector of Vec3i,Vector的每個(gè)元素包含了3個(gè)浮點(diǎn)數(shù),前2個(gè)是圓的中心坐標(biāo),最后一個(gè)是半徑。
三、輪廓的提取與描述
在目標(biāo)識(shí)別中我們首先要把感興趣的目標(biāo)提取出來(lái),而一般常見(jiàn)的步驟都是通過(guò)顏色或紋理提取出目標(biāo)的前景圖(一幅黑白圖像,目標(biāo)以白色顯示在圖像中),接下來(lái)我們要對(duì)前景圖進(jìn)行分析進(jìn)一步地把目標(biāo)提取出來(lái),而這里常常用到的就是提取目標(biāo)的輪廓。
OpenCV里提取目標(biāo)輪廓的函數(shù)是findContours,它的輸入圖像是一幅二值圖像,輸出的是每一個(gè)連通區(qū)域的輪廓點(diǎn)的集合:vector<vector<Point>>。外層vector的size代表了圖像中輪廓的個(gè)數(shù),里面vector的size代表了輪廓上點(diǎn)的個(gè)數(shù)。下面我們通過(guò)實(shí)例來(lái)看函數(shù)的用法。
int main()
{
using namespace cv;
Mat image=imread("../shape.png");
cvtColor(image,image,CV_BGR2GRAY);
vector<vector<Point>> contours;
// find
findContours(image,contours,CV_RETR_EXTERNAL,CV_CHAIN_APPROX_NONE);
// draw
Mat result(image.size(),CV_8U,Scalar(0));
drawContours(result,contours,-1,Scalar(255),2);
namedWindow("contours");
imshow("contours",result);
waitKey();
return 0;
}

上面程序中包含了2個(gè)函數(shù),第一個(gè)是查找輪廓函數(shù),它的第三個(gè)參數(shù)說(shuō)明查找輪廓的類(lèi)型,這里我們使用的是外輪廓,還可以查找所有輪廓,即包括一些孔洞的部分,像圖像人物胳膊與腰間形成的輪廓。第4個(gè)參數(shù)說(shuō)明了輪廓表示的方法,程序中的參數(shù)說(shuō)明輪廓包括了所有點(diǎn),也可以用其他參數(shù)讓有點(diǎn)直線的地方,只保存直線起始與終點(diǎn)的位置點(diǎn),具體參數(shù)用法可以參考手冊(cè)里函數(shù)的介紹。
第二個(gè)函數(shù)drawContours是一個(gè)畫(huà)輪廓的函數(shù),它的第3個(gè)參數(shù)程序里設(shè)置-1表示所有的輪廓都畫(huà),你也可以指定要畫(huà)的輪廓的序號(hào)。
提取到輪廓后,其實(shí)我們更關(guān)心的是如果把這些輪廓轉(zhuǎn)換為可以利用的特征,也就是涉及到輪廓的描述問(wèn)題,這時(shí)就有多種方法可以選擇,比如矢量化為多邊形、矩形、橢圓等。OpenCV里提供了一些這樣的函數(shù)。
// 輪廓表示為一個(gè)矩形
Rect r = boundingRect(Mat(contours[0]));
rectangle(result, r, Scalar(255), 2);
// 輪廓表示為一個(gè)圓
float radius;
Point2f center;
minEnclosingCircle(Mat(contours[1]), center, radius);
circle(result, Point(center), static_cast<int>(radius), Scalar(255), 2);
// 輪廓表示為一個(gè)多邊形
vector<Point> poly;
approxPolyDP(Mat(contours[2]), poly, 5, true);
vector<Point>::const_iterator itp = poly.begin();
while (itp != (poly.end() - 1))
{
line(result, *itp, *(itp + 1), Scalar(255), 2);
++itp;
}
line(result, *itp, *(poly.begin()), Scalar(255), 2);
// 輪廓表示為凸多邊形
vector<Point> hull;
convexHull(Mat(contours[3]), hull);
vector<Point>::const_iterator ith = hull.begin();
while (ith != (hull.end() - 1))
{
line(result, *ith, *(ith + 1), Scalar(255), 2);
++ith;
}
line(result, *ith, *(hull.begin()), Scalar(255), 2);
程序中我們依次畫(huà)了矩形、圓、多邊形和凸多邊形。最終效果如下:

對(duì)連通區(qū)域的分析到此遠(yuǎn)遠(yuǎn)沒(méi)有結(jié)束,我們可以進(jìn)一步計(jì)算每一個(gè)連通區(qū)域的其他屬性,比如:重心、中心矩等特征,這些內(nèi)容以后有機(jī)會(huì)展開(kāi)來(lái)寫(xiě)。
以下幾個(gè)函數(shù)可以嘗試:minAreaRect:計(jì)算一個(gè)最小面積的外接矩形,contourArea可以計(jì)算輪廓內(nèi)連通區(qū)域的面積;pointPolygenTest可以用來(lái)判斷一個(gè)點(diǎn)是否在一個(gè)多邊形內(nèi)。mathShapes可以比較兩個(gè)形狀的相似性,相當(dāng)有用的一個(gè)函數(shù)。
總結(jié)
以上就是本文關(guān)于C語(yǔ)言實(shí)現(xiàn)opencv提取直線、輪廓及ROI實(shí)例詳解的全部?jī)?nèi)容,希望對(duì)大家有所幫助。感興趣的朋友可以繼續(xù)參閱本站其他相關(guān)專題,如有不足之處,歡迎留言指出。感謝朋友們對(duì)本站的支持!
相關(guān)文章
C++ 虛函數(shù)和純虛函數(shù)的區(qū)別分析
這篇文章主要介紹了C++ 虛函數(shù)和純虛函數(shù)的區(qū)別,幫助大家更好的理解和學(xué)習(xí)c++的相關(guān)知識(shí),感興趣的朋友可以了解下2020-10-10
深入了解C++優(yōu)先隊(duì)列(priority_queue)的使用方法
在計(jì)算機(jī)科學(xué)中,優(yōu)先隊(duì)列是一種抽象數(shù)據(jù)類(lèi)型,它與隊(duì)列相似,但是每個(gè)元素都有一個(gè)相關(guān)的優(yōu)先級(jí)。C++中的優(yōu)先隊(duì)列是一個(gè)容器適配器(container adapter),它提供了一種在元素之間維護(hù)優(yōu)先級(jí)的方法。本文帶你深入了解C++優(yōu)先隊(duì)列的使用方法,需要的可以參考下2023-05-05
C++中實(shí)現(xiàn)OpenCV圖像分割與分水嶺算法
分水嶺算法是一種常用的圖像區(qū)域分割法,本文主要介紹了OpenCV圖像分割與分水嶺算法,使用C++實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的可以了解一下2021-06-06
c++中的單例類(lèi)模板的實(shí)現(xiàn)方法詳解
這篇文章主要介紹了c++中的單例類(lèi)模板的實(shí)現(xiàn)方法詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-03-03
C/C++中接收return返回來(lái)的數(shù)組元素方法示例
return是C++預(yù)定義的語(yǔ)句,它提供了種植函數(shù)執(zhí)行的一種放大,最近學(xué)習(xí)中遇到了相關(guān)return的內(nèi)容,覺(jué)著有必要總結(jié)一下,這篇文章主要給大家介紹了關(guān)于C/C++中如何接收return返回來(lái)的數(shù)組元素的相關(guān)資料,需要的朋友可以參考下。2017-12-12
VC運(yùn)用OPENGL加載BMP紋理圖的實(shí)現(xiàn)方法匯總
這篇文章主要介紹了VC運(yùn)用OPENGL加載BMP紋理圖的實(shí)現(xiàn)方法,對(duì)于更好的了解OpenGL很有幫助,需要的朋友可以參考下2014-07-07

