模板匹配就是在整个图像区域发现与给定子图像匹配的小块区域。
所以模板匹配首先需要一个模板图像T(给定的子图像)。
另外需要一个待检测的图像-源图像S。
工作方法,在带检测图像上,从左到右,从上向下计算模板图像与重叠子图像的匹配度,匹配程度越大,两者相同的可能性越大。
模板匹配是一种最原始、最基本的模式识别方法,研究某一特定对象物的图案位于图像的什么地方,进而识别对象物,这就是一个匹配问题。它是图像处理中最基本、最常用的匹配方法。
1)平方差匹配-CV_TM_SQDIFF
从名字来理解,平方差匹配就是通过计算每个像素点的差的平方的和,和数学中统计里面的平方差类似。但是因为我们要的只是一个值,所以我们最后不需要求平均。
如果相近,则每个差都很小,最终的和也很小,如果完全一致,则差为0,所以最好的匹配就是这个值为零的时候。值越大,匹配越差。
2)标准平方差匹配-CV_TM_SQDIFF_NORMED
这个只是对上面的进行了标准化处理,经过处理后,上面的值就不会太大。
3)相关匹配-CV_TM_CCORR
这类方法采用模板和图像间的乘法操作,所以较大的数表示匹配程度较高,0标识最坏的匹配效果。
4)标准相关匹配-CV_TM_CCORR_NORMED
这个只是对上面的进行了标准化处理,经过处理后,上面的值就不会太大。
5)相关匹配-CV_TM_CCOEFF
这类方法将模版对其均值的相对值与图像对其均值的相关值进行匹配,1表示完美匹配,-1表示糟糕的匹配,0表示没有任何相关性(随机序列)。
6)标准相关匹配-CV_TM_CCOEFF_NORMED
这个只是对上面的进行了标准化处理,经过处理后,上面的值就不会太大。
void matchTemplate(
InputArray image,
InputArray templ,
OutputArray result,
int method,
InputArray mask = noArray()
);
参数说明:
(1)InputArray类型的src ,运行搜索的图像。它必须是8位或32位浮点。
(2)InputArray类型的templ,已搜索模板。它不能大于源映像,并且具有相同的数据类型。。
(3)OutputArray类型的result,比较结果的映射。它必须是单通道32位浮点。
(4)int类型的method,指定比较方法的参数,请参见cv::TemplateMatchModes。
(5)InputArray类型的mask,搜索模板的掩码。它必须与temp具有相同的数据类型和大小。默认情况下不设置。
```cpp
void minMaxLoc(InputArray src, CV_OUT double* minVal,
CV_OUT double* maxVal=0, CV_OUT Point* minLoc=0,
CV_OUT Point* maxLoc=0, InputArray mask=noArray());
这个函数可以在一个矩阵中寻找最大点或最小点,并将位置返回回来。
综合示例:
#include
#include
#include
using namespace std;
using namespace cv;
Mat src, temp, dst;
int match_method = TM_SQDIFF;
int max_track = 5;
const char* INPUT_T = "input image";
const char* OUTPUT_T = "result image";
const char* match_t = "template match-demo";
void Match_Demo(int, void*);
int main(int argc, char** argv) {
// 待检测图像
src = imread("E:/cvpicture/1.png");
// 模板图像
temp = imread("E:/cvpicture/12.png");
if (src.empty() || temp.empty()) {
printf("could not load image...\n");
return -1;
}
namedWindow(INPUT_T, CV_WINDOW_AUTOSIZE);
namedWindow(OUTPUT_T, CV_WINDOW_NORMAL);
namedWindow(match_t, CV_WINDOW_AUTOSIZE);
imshow(INPUT_T, temp);
const char* trackbar_title = "Match Algo Type:";
createTrackbar(trackbar_title, OUTPUT_T, &match_method, max_track, Match_Demo);
Match_Demo(0, 0);
waitKey(0);
return 0;
}
void Match_Demo(int, void*) {
int width = src.cols - temp.cols + 1;
int height = src.rows - temp.rows + 1;
//创建结果输出矩阵
Mat result(width, height, CV_32FC1);
//进行匹配和标准化
matchTemplate(src, temp, result, match_method, Mat());
normalize(result, result, 0, 1, NORM_MINMAX, -1, Mat());
Point minLoc;
Point maxLoc;
double min, max;
src.copyTo(dst);
Point temLoc;
//通过函数minMaxLoc定位最佳匹配位置
minMaxLoc(result, &min, &max, &minLoc, &maxLoc, Mat());
// 对于方法 SQDIFF 和 SQDIFF_NORMED, 越小的数值代表更高的匹配结果. 而对于其他方法, 数值越大匹配越好
if (match_method == TM_SQDIFF || match_method == TM_SQDIFF_NORMED) {
temLoc = minLoc;
}
else {
temLoc = maxLoc;
}
// 绘制矩形
rectangle(dst, Rect(temLoc.x, temLoc.y, temp.cols, temp.rows), Scalar(0, 0, 255), 2, 8);
rectangle(result, Rect(temLoc.x, temLoc.y, temp.cols, temp.rows), Scalar(0, 0, 255), 2, 8);
imshow(OUTPUT_T, result);
imshow(match_t, dst);
}
轮廓发现是基于图像边缘提取的基础寻找对象轮廓的方法。所以边缘提取的阈值选定会影响最终轮廓发现结果。
对于opencv计算机视觉来说,轮廓发现就是通过对图像进行处理之后,提取物体的轮廓的操作。想要提取图像的轮廓,我们就肯定要先找到图像的边缘。
因此,在opencv中,我们想要获得图像的轮廓,分为两部分操作:
1)发现轮廓
2)绘制轮廓
在二值图像上发现轮廓使用API cv::findContours(
InputOutputArray binImg, // 输入图像,非0的像素被看成1,0的像素值保持不变,8-bit
OutputArrayOfArrays contours,// 全部发现的轮廓对象
OutputArray, hierachy// 图该的拓扑结构,可选,该轮廓发现算法正是基于图像拓扑结构实现。
int mode, // 轮廓返回的模式
int method,// 发现方法
Point offset=Point()// 轮廓像素的位移,默认(0, 0)没有位移
)
在二值图像上发现轮廓使用API cv::findContours之后对发现的轮廓数据进行绘制显示
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()// 轮廓位移,可选
)
综合示例:
(1)输入图像转为灰度图像cvtColor
(2)使用Canny进行边缘提取,得到二值图像
(3)使用findContours寻找轮廓
(4)使用drawContours绘制轮廓
#include
#include
#include
using namespace std;
using namespace cv;
Mat src, dst;
const char* output_win = "findcontours-demo";
int threshold_value = 100;
int threshold_max = 255;
RNG rng;
void Demo_Contours(int, void*);
int main(int argc, char** argv) {
src = imread("D:/vcprojects/images/happyfish.png");
if (src.empty()) {
printf("could not load image...\n");
return -1;
}
namedWindow("input-image", CV_WINDOW_AUTOSIZE);
namedWindow(output_win, CV_WINDOW_AUTOSIZE);
imshow("input-image", src);
cvtColor(src, src, CV_BGR2GRAY);
const char* trackbar_title = "Threshold Value:";
createTrackbar(trackbar_title, output_win, &threshold_value, threshold_max, Demo_Contours);
Demo_Contours(0, 0);
waitKey(0);
return 0;
}
void Demo_Contours(int, void*) {
Mat canny_output;
vector<vector<Point>> contours;
vector<Vec4i> hierachy;
Canny(src, canny_output, threshold_value, threshold_value * 2, 3, false);
findContours(canny_output, contours, hierachy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0));
dst = Mat::zeros(src.size(), CV_8UC3);
RNG rng(12345);
for (size_t i = 0; i < contours.size(); i++) {
Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
drawContours(dst, contours, i, color, 2, 8, hierachy, 0, Point(0, 0));
}
imshow(output_win, dst);
}
正常我们在提到轮廓截取出来时,一般需要是矩形的图像,这次我们就来学习一下轮廓周围绘制矩形。
目的:减少多边形轮廓点数。
原理:该算法递归进行,首先设定一个阈值,在点集的第一个点和最后一个点间拉一条线段,找出剩下的点集中离线段最远的一个点,如果该点到线段的距离小于阈值则舍弃中间的所有点,如果大于阈值。将该点作为中间点和最初的两个点生成两条线段,重复上述过程。
灰色点为省去的点。
基于RDP算法实现的减少多边形点数的API:
//对图像轮廓点进行多边形拟合
approxPolyDP(InputArray curve, OutputArray approxCurve, double epsilon, bool closed)
参数说明:
InputArray curve:一般是由图像的轮廓点组成的点集
OutputArray approxCurve:表示输出的多边形点集
double epsilon:主要表示输出的精度,就是另个轮廓点之间最大距离数,5,6,7,8,,,
bool closed:表示输出的多边形是否封闭
轮廓周围绘制矩形API:
//得到轮廓周围最小矩形左上交点坐标和右下角点坐标,绘制一个矩形。返回一个矩形RECT对象
cv::boundingRect(InputArray points)
参数说明:
points 二维点集,点的序列或向量。
//得到一个最小旋转的矩形,返回旋转矩形,返回4个点
cv::minAreaRect(InputArray points)
minAreaRect,得到最小的外接矩形
参数说明:
points 二维点集,点的序列或向量。
轮廓周围绘制圆和椭圆API:
//绘制圆
cv::minEnclosingCircle(InputArray points, Point2f& center,float& radius)
参数说明:
points: 二维点集,点的序列或向量。
Point2f& center:圆心位置,返回的所有圆心放进容器。
float& radius :圆的半径,返回的所有圆的半径放进容器。
//绘制椭圆,得到最小椭圆,返回一个椭圆对象
cv::fitEllipse(InputArray points)
参数说明:
points: 二维点集,点的序列或向量。
1)首先将图像变为二值图像
2)发现轮廓,找到图像轮廓
3)通过相关API在轮廓点上找到最小包含矩形和圆,旋转矩形与椭圆。
4)绘制它们。
综合示例:
#include
#include
#include
using namespace std;
using namespace cv;
Mat src, gray_src, drawImg;
int threshold_v = 170;
int threshold_max = 255;
const char* output_win = "rectangle-demo";
RNG rng(12345);
void Contours_Callback(int, void*);
int main(int argc, char** argv) {
src = imread("D:/vcprojects/images/hotball.png");
if (!src.data) {
printf("could not load image...\n");
return -1;
}
cvtColor(src, gray_src, CV_BGR2GRAY);
blur(gray_src, gray_src, Size(3, 3), Point(-1, -1));
const char* source_win = "input image";
namedWindow(source_win, CV_WINDOW_AUTOSIZE);
namedWindow(output_win, CV_WINDOW_AUTOSIZE);
imshow(source_win, src);
createTrackbar("Threshold Value:", output_win, &threshold_v, threshold_max, Contours_Callback);
Contours_Callback(0, 0);
waitKey(0);
return 0;
}
void Contours_Callback(int, void*) {
Mat binary_output;
vector<vector<Point>> contours;
vector<Vec4i> hierachy;
threshold(gray_src, binary_output, threshold_v, threshold_max, THRESH_BINARY);
//imshow("binary image", binary_output);
//找轮廓
findContours(binary_output, contours, hierachy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(-1, -1));
vector<vector<Point>> contours_ploy(contours.size()); //存放RDP处理后的轮廓点数
vector<Rect> ploy_rects(contours.size());//存放Rect矩形对象
vector<Point2f> ccs(contours.size());//存放圆心
vector<float> radius(contours.size());//存放圆的半径
vector<RotatedRect> minRects(contours.size());
vector<RotatedRect> myellipse(contours.size());
for (size_t i = 0; i < contours.size(); i++) {
approxPolyDP(Mat(contours[i]), contours_ploy[i], 3, true);
ploy_rects[i] = boundingRect(contours_ploy[i]);
minEnclosingCircle(contours_ploy[i], ccs[i], radius[i]);
if (contours_ploy[i].size() > 5) {
myellipse[i] = fitEllipse(contours_ploy[i]);
minRects[i] = minAreaRect(contours_ploy[i]);
}
}
// draw it
drawImg = Mat::zeros(src.size(), src.type());
Point2f pts[4];
for (size_t t = 0; t < contours.size(); t++) {
Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
//rectangle(drawImg, ploy_rects[t], color, 2, 8);
//circle(drawImg, ccs[t], radius[t], color, 2, 8);
if (contours_ploy[t].size() > 5) {
ellipse(drawImg, myellipse[t], color, 1, 8);
minRects[t].points(pts);
for (int r = 0; r < 4; r++) {
line(drawImg, pts[r], pts[(r + 1) % 4], color, 1, 8);
}
}
}
imshow(output_win, drawImg);
return;
}
图像识别的一个核心问题是图像的特征提取,简单描述即为用一组简单的数据(数据描述量)来描述整个图像,这组数据月简单越有代表性越好。良好的特征不受光线、噪点、几何形变的干扰,图像识别技术的发展中,不断有新的描述图像特征提出,而图像不变矩就是其中一个。
从图像中计算出来的矩通常描述了图像不同种类的几何特征如:大小、灰度、方向、形状、重心等,图像矩广泛应用于模式识别、目标分类、目标识别与防伪估计、图像编码与重构等领域。
图像的几何矩:
1)图像平面上每个像素点的值看成该处的概率密度;
2)对某点求期望,就是图像在该点处的矩;
3)图像矩一般都是原点矩:
一阶矩和零阶矩,计算形状的重心;
二阶矩,计算形状的方向。
OpenCV中提供了moments()来计算图像中的中心矩(最高到三阶),HuMoments()用于由中心矩计算Hu矩,同时配合函数contourArea函数计算轮廓面积和arcLength来计算轮廓或曲线长度。
//计算位置质心
cv::moments (InputArray array,bool binaryImage = false )
参数说明:
array:输入数组,可以是光栅图像(单通道,8-bit或浮点型二维数组),或者是一个二维数组(1 X N或N X 1),二维数组类型为Point或Point2f;
binaryImage:默认值是false,如果为true,则所有非零的像素都会按值1对待,也就是说相当于对图像进行了二值化处理,阈值为1,此参数仅对图像有效。
//该函数使用Green formula计算轮廓面积,返回面积和非零像素数量
double cv::contourArea (InputArray contour,bool oriented = false )
参数说明:
contour:是一个向量,二维点,可以是vector或Mat类型;
oriented:有默认值false,面向区域标识符,如果为true,该函数返回一个带符号的面积,其正负取决于轮廓的方向(顺时针还是逆时针)。根据这个特性可以根据面积的符号来确定轮廓的位置。如果是默认值false,则面积以绝对值的形式返回。
//用于计算封闭轮廓的周长或曲线的长度
double cv::arcLength ( InputArray curve, bool closed )
参数说明:
curve:输入二维点集,可以是vector或Mat类型 ;
closed:曲线是否封闭的标志位,true则封闭否则不封闭。
1)提取图像边缘;
2)发现轮廓;
3)计算每个轮廓对象的矩;
4)计算每个对象的中心、弧长、面积。
综合示例:
include <opencv2/opencv.hpp>
#include
#include
using namespace std;
using namespace cv;
Mat src, gray_src;
int threshold_value = 80;
int threshold_max = 255;
const char* output_win = "image moents demo";
RNG rng(12345);
void Demo_Moments(int, void*);
int main(int argc, char** argv) {
src = imread("D:/vcprojects/images/circle.png");
if (!src.data) {
printf("could not load image...\n");
return -1;
}
cvtColor(src, gray_src, CV_BGR2GRAY);
GaussianBlur(gray_src, gray_src, Size(3, 3), 0, 0);
char input_win[] = "input image";
namedWindow(input_win, CV_WINDOW_AUTOSIZE);
namedWindow(output_win, CV_WINDOW_AUTOSIZE);
imshow(input_win, src);
createTrackbar("Threshold Value : ", output_win, &threshold_value, threshold_max, Demo_Moments);
Demo_Moments(0, 0);
waitKey(0);
return 0;
}
void Demo_Moments(int, void*) {
Mat canny_output;
vector<vector<Point>> contours;
vector<Vec4i> hierachy;
Canny(gray_src, canny_output, threshold_value, threshold_value * 2, 3, false);
findContours(canny_output, contours, hierachy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0));
vector<Moments> contours_moments(contours.size());
vector<Point2f> ccs(contours.size());
for (size_t i = 0; i < contours.size(); i++) {
contours_moments[i] = moments(contours[i]);
ccs[i] = Point(static_cast<float>(contours_moments[i].m10 / contours_moments[i].m00), static_cast<float>(contours_moments[i].m01 / contours_moments[i].m00));
}
Mat drawImg;// = Mat::zeros(src.size(), CV_8UC3);
src.copyTo(drawImg);
for (size_t i = 0; i < contours.size(); i++) {
if (contours[i].size() < 100) {
continue;
}
Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
printf("center point x : %.2f y : %.2f\n", ccs[i].x, ccs[i].y);
printf("contours %d area : %.2f arc length : %.2f\n", i, contourArea(contours[i]), arcLength(contours[i], true));
drawContours(drawImg, contours, i, color, 2, 8, hierachy, 0, Point(0, 0));
circle(drawImg, ccs[i], 2, color,2, 8);
}
imshow(output_win, drawImg);
return;
}
测试一个点是否在给定的多边形内部,边缘,或者外部。
double pointPolygonTest(
InputArray contour, // 输入的轮廓
Point2f pt, // 测试点
bool measureDist // 是否返回距离值,如果是false,1表示在内面,0表示在边界上,-1表示在外部,true返回实际距离
)
返回数据为double类型。
1)构建一张400x400大小的图片, Mat::Zero(400, 400, CV_8UC1);
2)画上一个六边形的闭合区域line;
3)发现轮廓;
4)对图像中所有像素点做点 多边形测试,得到距离,归一化后显示。
综合示例:
#include
#include
#include
using namespace std;
using namespace cv;
int main(int argc, char** argv) {
const int r = 100;
Mat src = Mat::zeros(r * 4, r * 4, CV_8UC1);
vector<Point2f> vert(6);
vert[0] = Point(3 * r / 2, static_cast<int>(1.34*r));
vert[1] = Point(1 * r, 2 * r);
vert[2] = Point(3 * r / 2, static_cast<int>(2.866*r));
vert[3] = Point(5 * r / 2, static_cast<int>(2.866*r));
vert[4] = Point(3 * r, 2 * r);
vert[5] = Point(5 * r / 2, static_cast<int>(1.34*r));
for (int i = 0; i < 6; i++) {
line(src, vert[i], vert[(i + 1) % 6], Scalar(255), 3, 8, 0);
}
vector<vector<Point>> contours;
vector<Vec4i> hierachy;
Mat csrc;
src.copyTo(csrc);
findContours(csrc, contours, hierachy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0));
Mat raw_dist = Mat::zeros(csrc.size(), CV_32FC1);
for (int row = 0; row < raw_dist.rows; row++) {
for (int col = 0; col < raw_dist.cols; col++) {
double dist = pointPolygonTest(contours[0], Point2f(static_cast<float>(col), static_cast<float>(row)), true);
raw_dist.at<float>(row, col) = static_cast<float>(dist);
}
}
double minValue, maxValue;
minMaxLoc(raw_dist, &minValue, &maxValue, 0, 0, Mat());
Mat drawImg = Mat::zeros(src.size(), CV_8UC3);
for (int row = 0; row < drawImg.rows; row++) {
for (int col = 0; col < drawImg.cols; col++) {
float dist = raw_dist.at<float>(row, col);
if (dist > 0) {
drawImg.at<Vec3b>(row, col)[0] = (uchar)(abs(1.0 - (dist / maxValue)) * 255);
}
else if (dist < 0) {
drawImg.at<Vec3b>(row, col)[2] = (uchar)(abs(1.0 - (dist / minValue)) * 255);
} else {
drawImg.at<Vec3b>(row, col)[0] = (uchar)(abs(255 - dist));
drawImg.at<Vec3b>(row, col)[1] = (uchar)(abs(255 - dist));
drawImg.at<Vec3b>(row, col)[2] = (uchar)(abs(255 - dist));
}
}
}
const char* output_win = "point polygon test demo";
char input_win[] = "input image";
namedWindow(input_win, CV_WINDOW_AUTOSIZE);
namedWindow(output_win, CV_WINDOW_AUTOSIZE);
imshow(input_win, src);
imshow(output_win, drawImg);
waitKey(0);
return 0;
}