基於Opencv的物體輪廓識別
OpenCV是一個很強大的視覺庫,因此本次我們根據一個小例子來說明如何簡單的繪製一個物體的輪廓。
第一步:二值化
就是將影象上的畫素點的灰度值設定為0或255,也就是將整個影象呈現出明顯的只有黑和白的視覺效果,便於接下來的操作。
函式原型:
double threshold(InputArray src, OutputArray dst, double thresh, double maxval, int type);
第一個引數,InputArray型別的src,輸入陣列,填單通道 , 8或32位浮點型別的Mat即可。
第二個引數,OutputArray型別的dst,函式呼叫後的運算結果存在這裡,即這個引數用於存放輸出結果,且和第一個引數中的Mat變數有一樣的尺寸和型別。
第三個引數,double型別的thresh,閾值的具體值。
第四個引數,double型別的maxval,當第五個引數閾值型別type取 THRESH_BINARY 或THRESH_BINARY_INV閾值型別時的最大值.
第五個引數,int型別的type,閾值型別,。
其它引數很好理解,我們來看看第五個引數,第五引數有以下幾種型別
0: THRESH_BINARY 當前點值大於閾值時,取Maxval,也就是第四個引數,下面再不說明,否則設定為0
1: THRESH_BINARY_INV 當前點值大於閾值時,設定為0,否則設定為Maxval
2: THRESH_TRUNC 當前點值大於閾值時,設定為閾值,否則不改變
3: THRESH_TOZERO 當前點值大於閾值時,不改變,否則設定為0
4: THRESH_TOZERO_INV 當前點值大於閾值時,設定為0,否則不改變
第二步:形態學操作
用來消除噪聲的影響。
函式原型如下:
void morphologyEx(InputArray src, OutputArray dst, int op, InputArray kernel, Point anchor=Point(-1,-1), intiterations=1, int borderType=BORDER_CONSTANT, const Scalar& borderValue=morphologyDefaultBorderValue() );
第一個引數,InputArray型別的src,輸入影象,即源影象,填Mat類的物件即可。影象位深應該為以下五種之一:CV_8U, CV_16U,CV_16S, CV_32F 或CV_64F。
第二個引數,OutputArray型別的dst,即目標影象,函式的輸出引數,需要和源圖片有一樣的尺寸和型別。
第三個引數,int型別的op,表示形態學運算的型別,可以是如下之一的識別符號:
MORPH_OPEN – 開運算(Opening operation)
MORPH_CLOSE – 閉運算(Closing operation)
MORPH_GRADIENT -形態學梯度(Morphological gradient)
MORPH_TOPHAT - “頂帽”(“Top hat”)
MORPH_BLACKHAT - “黑帽”(“Black hat“)
第四個引數,InputArray型別的kernel,形態學運算的核心。若為NULL時,表示的是使用參考點位於中心3x3的核。我們一般使用函式 getStructuringElement配合這個引數的使用。getStructuringElement函式會返回指定形狀和尺寸的結構元素(核心矩陣)。
第五個引數,Point型別的anchor,錨的位置,其有預設值(-1,-1),表示錨位於中心。
第六個引數,int型別的iterations,迭代使用函式的次數,預設值為1。
第七個引數,int型別的borderType,用於推斷影象外部畫素的某種邊界模式。注意它有預設值BORDER_ CONSTANT。
第八個引數,const Scalar&型別的borderValue,當邊界為常數時的邊界值,有預設值morphologyDefaultBorderValue(),一般不管。
第三步:輪廓發現
函式原型如下:
findContours( InputOutputArray image, OutputArrayOfArrays contours , OutputArray hierarchy, int mode, int method, Point offset=Point());
第一個引數:image,單通道影象矩陣,可以是灰度圖,但更常用的是二值影象,一般是經過Canny、拉普拉斯等邊緣檢測運算元處理過的二值影象;
第二個引數:contours,定義為“vector<vector<Point>> contours”,是一個向量,並且是一個雙重向量,向量 內每個元素儲存了一組由連續的Point點構成的點的集合的向量,每一組Point點集就是一個輪廓。 有多少輪廓,向量contours就有多少元素。
第三個引數:hierarchy,定義為“vector<Vec4i> hierarchy”,先來看一下Vec4i的定義: typedef Vec<int, 4> Vec4i; Vec4i是Vec<int,4>的別名,定義了一個“向量內每一個元素包含了4個int型變數”的向量。 所以從定義上看,hierarchy也是一個向量,向量內每個元素儲存了一個包含4個int整型的陣列。 向量hiararchy內的元素和輪廓向量contours內的元素是一一對應的,向量的容量, hierarchy向量內每一個元素的4個int型變數——hierarchy[i][0] ~hierarchy[i][3],分別表示 i個輪廓的後一個輪廓、前一個輪廓、父輪廓、內嵌輪廓的索引編號。如果當前輪廓沒有對應的後一個輪廓、前一個輪廓、父輪廓或內嵌輪廓的話,則hierarchy[i][0] ~hierarchy[i][3]的相應位被設定為 預設值-1。
第四個引數:int型的mode,定義輪廓的檢索模式
取值一:CV_RETR_EXTERNAL只檢測最外圍輪廓,包含在外圍輪廓內的內圍輪廓被忽略
取值二:CV_RETR_LIST 檢測所有的輪廓,包括內圍、外圍輪廓,但是檢測到的輪廓不建立等級關 系,彼此之間獨立,沒有等級關係,這就意味著這個檢索模式下不存在父輪廓或內嵌輪廓,所以hierarchy向量內所有元素的第3、第4個分量都會被置為-1,具體下文會講到
取值三:CV_RETR_CCOMP 檢測所有的輪廓,但所有輪廓只建立兩個等級關係,外圍為頂層,若外圍 內的內圍輪廓還包含了其他的輪廓資訊,則內圍內的所有輪廓均歸屬於頂層
取值四:CV_RETR_TREE, 檢測所有輪廓,所有輪廓建立一個等級樹結構。外層輪廓包含內層輪廓,內層輪廓還可以繼續包含內嵌輪廓。
第五個引數:int型的method,定義輪廓的近似方法:
取值一:CV_CHAIN_APPROX_NONE 儲存物體邊界上所有連續的輪廓點到contours向量內
取值二:CV_CHAIN_APPROX_SIMPLE 僅儲存輪廓的拐點資訊,把所有輪廓拐點處的點儲存入contours 向量內,拐點與拐點之間直線段上的資訊點不予保留
取值三和四:CV_CHAIN_APPROX_TC89_L1,CV_CHAIN_APPROX_TC89_KCOS使用teh-Chinl chain 近 似演算法
第六個引數:Point偏移量,所有的輪廓資訊相對於原始影象對應點的偏移量,相當於在每一個檢測出的輪廓點上加 上該偏移量,並且Point還可以是負值
第四步:繪製輪廓
函式原型如下:
void drawContours(InputOutputArray image, InputArrayOfArrays contours, int contourIdx, const Scalar& color, int thickness=1, int lineType=8, InputArray hierarchy=noArray(), int maxLevel=INT_MAX, Point offset=Point() )
其中第一個引數image表示目標影象,
第二個引數contours表示輸入的輪廓組,每一組輪廓由點vector構成,
第三個引數contourIdx指明畫第幾個輪廓,如果該引數為負值,則畫全部輪廓,
第四個引數color為輪廓的顏色,
第五個引數thickness為輪廓的線寬,如果為負值或CV_FILLED表示填充輪廓內部,
第六個引數lineType為線型,
第七個引數為輪廓結構資訊,
第八個引數為maxLevel
具體實現的程式碼如下:
#include <opencv2/opencv.hpp>
#include <iostream>
#include <math.h>
using namespace cv;
using namespace std;
int main(int argc, char** argv) {
Mat src = imread("E:\\pictured\\cat.jpg");
if (src.empty())
{
printf("could not load image...\n");
return 0;
}
namedWindow("input image", CV_WINDOW_AUTOSIZE);
imshow("input image", src);
// 二值處理
Mat gray_src, binary, dst;
cvtColor(src, gray_src, COLOR_BGR2GRAY);
threshold(gray_src, binary, 0, 255, THRESH_BINARY_INV | THRESH_OTSU);
imshow("binary image", binary);
// 形態學操作
Mat kernel = getStructuringElement(MORPH_RECT, Size(5, 5), Point(-1, -1));
morphologyEx(binary, dst, MORPH_CLOSE, kernel, Point(-1, -1), 3);
imshow("morphology", dst);
// 輪廓發現
bitwise_not(dst, dst, Mat());
vector<vector<Point>> contours;
vector<Vec4i> hireachy;
findContours(dst, contours, hireachy, CV_RETR_TREE, CHAIN_APPROX_SIMPLE, Point());
// 輪廓繪t制
int width = src.cols;
int height = src.rows;
Mat drawImage = Mat::zeros(src.size(), CV_8UC3);
for (size_t t = 0; t < contours.size(); t++) {
Rect rect = boundingRect(contours[t]);
if (rect.width > width / 2 && rect.width < width - 5) {
drawContours(drawImage, contours, static_cast<int>(t), Scalar(0, 0, 255), 2, 8, hireachy, 0, Point());
}
}
imshow("contours", drawImage);
vector<Vec4i> lines;
Mat contoursImg;
int accu = min(width*0.5, height*0.5);
cvtColor(drawImage, contoursImg, COLOR_BGR2GRAY);
HoughLinesP(contoursImg, lines, 1, CV_PI / 180.0, accu, accu, 0);
Mat linesImage = Mat::zeros(src.size(), CV_8UC3);
for (size_t t = 0; t < lines.size(); t++) {
Vec4i ln = lines[t];
line(linesImage, Point(ln[0], ln[1]), Point(ln[2], ln[3]), Scalar(0, 0, 255), 2, 8, 0);
}
printf("number of lines : %d\n", lines.size());
imshow("lines image", linesImage);
// 尋找與定位上下左右四條直線
int deltah = 0;
Vec4i topLine, bottomLine;
Vec4i leftLine, rightLine;
for (int i = 0; i < lines.size(); i++) {
Vec4i ln = lines[i];
deltah = abs(ln[3] - ln[1]);
if (ln[3] < height / 2.0 && ln[1] < height / 2.0 && deltah < accu - 1) {
if (topLine[3] > ln[3] && topLine[3]>0) {
topLine = lines[i];
}
else {
topLine = lines[i];
}
}
if (ln[3] > height / 2.0 && ln[1] > height / 2.0 && deltah < accu - 1) {
bottomLine = lines[i];
}
if (ln[0] < width / 2.0 && ln[2] < width / 2.0) {
leftLine = lines[i];
}
if (ln[0] > width / 2.0 && ln[2] > width / 2.0) {
rightLine = lines[i];
}
}
cout << "top line : p1(x, y) = " << topLine[0] << "," << topLine[1] << " p2(x, y) = " << topLine[2] << "," << topLine[3] << endl;
cout << "bottom line : p1(x, y) = " << bottomLine[0] << "," << bottomLine[1] << " p2(x, y) = " << bottomLine[2] << "," << bottomLine[3] << endl;
cout << "left line : p1(x, y) = " << leftLine[0] << "," << leftLine[1] << " p2(x, y) = " << leftLine[2] << "," << leftLine[3] << endl;
cout << "right line : p1(x, y) = " << rightLine[0] << "," << rightLine[1] << " p2(x, y) = " << rightLine[2] << "," << rightLine[3] << endl;
// 擬合四條直線方程
float k1, c1;
k1 = float(topLine[3] - topLine[1]) / float(topLine[2] - topLine[0]);
c1 = topLine[1] - k1*topLine[0];
float k2, c2;
k2 = float(bottomLine[3] - bottomLine[1]) / float(bottomLine[2] - bottomLine[0]);
c2 = bottomLine[1] - k2*bottomLine[0];
float k3, c3;
k3 = float(leftLine[3] - leftLine[1]) / float(leftLine[2] - leftLine[0]);
c3 = leftLine[1] - k3*leftLine[0];
float k4, c4;
k4 = float(rightLine[3] - rightLine[1]) / float(rightLine[2] - rightLine[0]);
c4 = rightLine[1] - k4*rightLine[0];
// 四條直線交點
Point p1; // 左上角
p1.x = static_cast<int>((c1 - c3) / (k3 - k1));
p1.y = static_cast<int>(k1*p1.x + c1);
Point p2; // 右上角
p2.x = static_cast<int>((c1 - c4) / (k4 - k1));
p2.y = static_cast<int>(k1*p2.x + c1);
Point p3; // 左下角
p3.x = static_cast<int>((c2 - c3) / (k3 - k2));
p3.y = static_cast<int>(k2*p3.x + c2);
Point p4; // 右下角
p4.x = static_cast<int>((c2 - c4) / (k4 - k2));
p4.y = static_cast<int>(k2*p4.x + c2);
cout << "p1(x, y)=" << p1.x << "," << p1.y << endl;
cout << "p2(x, y)=" << p2.x << "," << p2.y << endl;
cout << "p3(x, y)=" << p3.x << "," << p3.y << endl;
cout << "p4(x, y)=" << p4.x << "," << p4.y << endl;
// 顯示四個點座標
circle(linesImage, p1, 2, Scalar(255, 0, 0), 2, 8, 0);
circle(linesImage, p2, 2, Scalar(255, 0, 0), 2, 8, 0);
circle(linesImage, p3, 2, Scalar(255, 0, 0), 2, 8, 0);
circle(linesImage, p4, 2, Scalar(255, 0, 0), 2, 8, 0);
line(linesImage, Point(topLine[0], topLine[1]), Point(topLine[2], topLine[3]), Scalar(0, 255, 0), 2, 8, 0);
imshow("four corners", linesImage);
// 透視變換
vector<Point2f> src_corners(4);
src_corners[0] = p1;
src_corners[1] = p2;
src_corners[2] = p3;
src_corners[3] = p4;
vector<Point2f> dst_corners(4);
dst_corners[0] = Point(0, 0);
dst_corners[1] = Point(width, 0);
dst_corners[2] = Point(0, height);
dst_corners[3] = Point(width, height);
// 獲取透視變換矩陣
Mat resultImage;
Mat warpmatrix = getPerspectiveTransform(src_corners, dst_corners);
warpPerspective(src, resultImage, warpmatrix, resultImage.size(), INTER_LINEAR);
namedWindow("Final Result", CV_WINDOW_AUTOSIZE);
imshow("Final Result", resultImage);
waitKey(0);
return 0;
}