【OpenCV學習筆記】之影象輪廓特徵與影象的矩
轉載: https://blog.csdn.net/zhu_hongji/article/details/81699736
一、影象的輪廓(Contours of Image)
輪廓可以說是一個很好的影象目標的外部特徵,這種特徵對於我們進行影象分析,目標識別和理解等更深層次的處理都有很重要的意義。那麼,怎麼取提取輪廓呢?
輪廓提取的基本原理:
(針對二值化的輪廓提取是這樣的)對於一幅背景為白色、目標為黑色的二值影象,如果在圖中找到一個黑色點,且它的8鄰域(或4鄰域)也均為黑色,則說明該點是目標的內部點,將其置為白色,視覺上就像內部被掏空一樣;否則保持黑色不變,該點是目標的邊界點。
這裡順帶提下邊緣檢測,和輪廓提取的區別:
邊緣檢測主要是通過一些手段檢測數字影象中明暗變化劇烈(即梯度變化比較大)畫素點,偏向於影象中畫素點的變化。如canny邊緣檢測,結果通常儲存在和源圖片一樣尺寸和型別的邊緣圖中。
輪廓檢測指檢測影象中的物件邊界,更偏向於關注上層語義物件。如OpenCV中的findContours()函式, 它會得到每一個輪廓並以點向量方式儲存,除此也得到一個影象的拓撲資訊,即一個輪廓的後一個輪廓、前一個輪廓、父輪廓和內嵌輪廓的索引編號。
1.1 發現與繪製輪廓
在OpenCV裡面利用findContours()函式和drawContours()函式實現這一功能。
API介紹:
C++:void findContours(InputOutputArray image, OutputArrayOfArrays contours, OutputArray hierarchy, int mode, int method, Point offset = Point() )
引數詳解:
引數一: image,單通道影象矩陣,可以是灰度圖,但更常用的是二值影象,一般是經過Canny、拉普拉斯等邊緣檢測運算元處理過的二值影象;
引數二: contours是一個向量,並且是一個雙重向量,向量內每個元素儲存了一組由連續的Point點構成的點的集合的向量,每一組Point點集就是一個輪廓。有多少輪廓,向量contours就有多少元素。
引數三: 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還可以是負值
drawContours(
//InputOutputArray binImg, // 輸出影象
//OutputArrayOfArrays contours,// 全部發現的輪廓物件
//Int contourIdx// 輪廓索引號
//const Scalar & color,// 繪製時候顏色
//int thickness,// 繪製線寬
//int lineType,// 線的型別LINE_8
//InputArray hierarchy,// 拓撲結構圖
//int maxlevel,// 最大層數, 0只繪製當前的,1表示繪製繪製當前及其內嵌的輪廓
//Point offset = Point()// 輪廓位移,可選
)
示例程式
//輪廓發現(find contour)
//輪廓發現是基於影象邊緣提取的基礎尋找物件輪廓的方法。所以邊緣提取的閾值選定會影響最終輪廓發現結果
//步驟:輸入影象轉為灰度影象cvtColor
//使用Canny進行邊緣提取,得到二值影象
//使用findContours尋找輪廓
//使用drawContours繪製輪廓
#include"stdafx.h"
#include<opencv2/opencv.hpp>
#include<iostream>
#include<math.h>
using namespace std;
using namespace cv;
Mat src, dst;
char input[] = "gray image";
int threshold_value = 100;
int threshold_max = 255;
void find_contour_demo(int, void*);
int main(int argc, char*argv)
{
src = imread("C:\\Users\\59235\\Desktop\\imag\\mixed_03.png");
if (!src.data)
{
printf("could not load image...\n");
return -1;
}
namedWindow(input, CV_WINDOW_AUTOSIZE);
namedWindow("result", CV_WINDOW_AUTOSIZE);
//輸入影象轉為灰度影象
cvtColor(src, src, CV_BGR2GRAY);
imshow(input, src);
const char*thresh = "threshold value";
createTrackbar(thresh, input, &threshold_value, threshold_max, find_contour_demo);
find_contour_demo(0, 0);
waitKey(0);
return 0;
}
void find_contour_demo(int, void*)
{
//使用Canny進行邊緣提取,得到二值影象
Canny(src, dst, threshold_value, threshold_value * 2, 3, false);
imshow("canny detection", dst);
//使用findContours尋找輪廓
vector<vector<Point>> contours;
vector<Vec4i> hierachy;
findContours(dst, contours, hierachy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0));
//findContours(
//InputOutputArray binImg, // 輸入影象,非0的畫素被看成1,0的畫素值保持不變,8-bit
//OutputArrayOfArrays contours,// 全部發現的輪廓物件
//OutputArray, hierachy// 圖該的拓撲結構,可選,該輪廓發現演算法正是基於影象拓撲結構實現。
//int mode, // 輪廓返回的模式
//int method,// 發現方法
//Point offset = Point()// 輪廓畫素的位移,預設(0, 0)沒有位移)
//使用drawContours繪製輪廓
Mat drawImage = Mat::zeros(src.size(), CV_8UC3);
RNG rng(12345);
for (int i = 0; i < contours.size(); i++)
{
Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
drawContours(drawImage, contours, i, color, 2, LINE_AA, hierachy, 0, Point(0, 0));
//drawContours(
//InputOutputArray binImg, // 輸出影象
//OutputArrayOfArrays contours,// 全部發現的輪廓物件
//Int contourIdx// 輪廓索引號
//const Scalar & color,// 繪製時候顏色
//int thickness,// 繪製線寬
//int lineType,// 線的型別LINE_8
//InputArray hierarchy,// 拓撲結構圖
//int maxlevel,// 最大層數, 0只繪製當前的,1表示繪製繪製當前及其內嵌的輪廓
//Point offset = Point()// 輪廓位移,可選
}
imshow("result", drawImage);
return;
}
效果圖: (灰度圖)
(canny邊緣檢測圖)
(繪製輪廓圖)
總結:
OpenCV提取輪廓之後,還可以進行許多操作:
ArcLength() 計算輪廓長度
ContourArea() 計算輪廓區域的面積
BoundingRect() 輪廓的外包矩形
ConvexHull() 提取輪廓的凸包
IsContourConvex() 測試輪廓的凸性
MinAreaRect() 輪廓的最小外包矩形
MinEnclosingCircle() 輪廓的最小外包圓
fitEllipse()用橢圓擬合二維點集
approxPolyDP()逼近多邊形曲線
二、影象的矩(Image Moments)
矩的概念介紹
矩函式在影象分析中有著廣泛的應用,如模式識別、目標分類、目標識別與方位估計、影象的編碼與重構等。從一幅影象計算出來的矩集,不僅可以描述影象形狀的全域性特徵,而且可以提供大量關於該影象不同的幾何特徵資訊,如大小,位置、方向和形狀等。影象矩這種描述能力廣泛應用於各種影象處理、計算機視覺和機器人技術領域的目標識別與方位估計中。
一階矩:與形狀有關;
二階矩:顯示曲線圍繞直線平均值的擴充套件程度;
三階矩:關於平均值的對稱性測量;由二階矩和三階矩可以匯出7個不變矩。而不變矩是影象的統計特性,滿足平移、伸縮、旋轉均不變的不變性、在影象識別領域得到廣泛的應用。
1 、空間矩/幾何矩
空間矩的實質為面積或者質量。可以通過一階矩計算質心/重心。
重心(中心centers):
2、中心矩
中心矩體現的是影象強度的最大和最小方向(中心矩可以構建影象的協方差矩陣),其只具有平移不變性,所以用中心矩做匹配效果不會很好。
3、歸一化的中心矩
歸一化後具有尺度不變性。
4、Hu矩
Hu矩由於具有尺度、旋轉、平移不變性,可以用來做匹配。
cv::moments 計算生成資料:
//影象矩:(Image Moments)
//步驟:提取影象邊緣
//發現輪廓
//計算每個輪廓物件的矩
//計算每個物件的中心、弧長、面積
#include"stdafx.h"
#include<opencv2/opencv.hpp>
#include<iostream>
#include<math.h>
using namespace cv;
using namespace std;
Mat src, dst, drawImage;
const char*result = "moments_demo";
int threshold_value = 120;
int threshold_max = 255;
RNG rng(12345);
void Moments_demo(int, void*);
int main(int argc, char*argv)
{
src = imread("C:\\Users\\59235\\Desktop\\imag\\mixed_01.png");
if (!src.data)
{
printf("could not load image...\n");
return -1;
}
char input[] = "gray image";
namedWindow(input, CV_WINDOW_AUTOSIZE);
namedWindow(result, CV_WINDOW_AUTOSIZE);
//輸入影象轉為灰度影象
cvtColor(src, dst, CV_BGR2GRAY);
GaussianBlur(dst, dst, Size(3, 3), 0, 0);
imshow(input, dst);
const char*thresh = "threshold value";
createTrackbar(thresh, result, &threshold_value, threshold_max, Moments_demo);
Moments_demo(0, 0);
waitKey(0);
return 0;
}
void Moments_demo(int, void*)
{
//提取影象邊緣
Mat canny_out;
Canny(dst, canny_out, threshold_value, threshold_value * 2, 3, false);
//imshow("canny image", canny_out);
//發現輪廓,找到影象輪廓
vector<vector<Point>> contours;
vector<Vec4i> hierachy;
findContours(canny_out, contours, hierachy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0));
//計算每個輪廓物件的矩
vector< Moments> contours_moments(contours.size());
vector<Point2f> centers(contours.size());
for (size_t i = 0; i < contours.size(); i++)
{
//計算矩
contours_moments[i] = moments(contours[i]);
//moments(InputArray array,//輸入資料
//bool binaryImage = false // 是否為二值影象
centers[i] = Point(static_cast<float>(contours_moments[i].m10 / contours_moments[i].m00), static_cast<float>(contours_moments[i].m01 / contours_moments[i].m00));
//影象中心Center(x0, y0)=(m10/m00,m01/m00)
}
src.copyTo(drawImage);
for (size_t i = 0; i < contours.size(); i++)
{
printf("centers point x:%.2f,y:%.2f\n", centers[i].x, centers[i].y);
printf("contours %d Area:%.2f Arc length:%.2f \n", i, contourArea(contours[i]), arcLength(contours[i], true));
//contourArea(InputArray contour,//輸入輪廓資料
//bool oriented// 預設false、返回絕對值)
//arcLength(InputArray curve,//輸入曲線資料
//bool closed// 是否是封閉曲線)
//考慮如何把資料顯示在原影象上
//double A;
//A=contourArea(contours[i]);
//ostringstream os;
//os << A;
//putText(drawImage,os.str,centers[i], CV_FONT_BLACK, 2.0, Scalar(0,0,255), 2, 8);
//依次含義:原圖,輸入字的內容,起始位置,字型,字的大小,顏色,線條大小粗 細,連線域
Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
drawContours(drawImage, contours, i, color, 2, LINE_AA, hierachy, 0, Point(0, 0));//繪製輪廓
circle(drawImage, centers[i], 2, color, 2, LINE_AA);//繪製圖形中心
}
imshow(result, drawImage);
return;
}
效果圖: