1.腐蚀操作
#include <iostream>
#include <opencv2\opencv.hpp>
using namespace cv;
using namespace std;
int main()
{
Mat SrcPic = imread("lena.jpg");
imshow("Src Pic", SrcPic);
/*创建结构元素的函数。"MORPH_RECT"表示创建一个矩形形状的结构元素。"MORPH_ELLIPSE"椭圆形,"MORPH_CROSS"十字形
* Size(15,15)是指定结构元素大小的参数,以像素为单位
*/
Mat element = getStructuringElement(MORPH_RECT, Size(15, 15));
Mat DstPic;
erode(SrcPic, DstPic, element); //腐蚀操作。element:定义腐蚀操作的内核形状和大小
imshow("腐蚀效果图", DstPic);
waitKey();
return 0;
}
2.canny边缘检测
思路:将原始图像转化为灰度图,用blur函数进行图像模糊以降噪,然后用canny函数进行边缘检测。
#include <iostream>
#include <opencv2\opencv.hpp>
using namespace cv;
using namespace std;
int main()
{
Mat SrcPic = imread("lena.jpg");
imshow("Src Pic", SrcPic);
Mat DstPic, edge, grayImage;
//创建与src同类型和同大小的矩阵
DstPic.create(SrcPic.size(), SrcPic.type());
//将原始图转化为灰度图
cvtColor(SrcPic, grayImage, COLOR_BGR2GRAY);
//先使用3*3内核来降噪
blur(grayImage, edge, Size(3, 3));
//运行canny算子
Canny(edge, edge, 3, 9, 3);
imshow("边缘提取效果", edge);
waitKey();
return 0;
}
3.膨胀,腐蚀,开闭运算
腐蚀和膨胀是最基本的形态学运算。腐蚀和膨胀是针对白色部分(高亮部分)而言的。膨胀就是对图像高亮部分进行“领域扩张”,效果图拥有比原图更大的高亮区域;腐蚀是原图中的高亮区域被蚕食,效果图拥有比原图更小的高亮区域。
开运算:先腐蚀再膨胀,用来消除小物体
闭运算:先膨胀再腐蚀,用于排除小型黑洞
形态学梯度:就是膨胀图与俯视图之差,用于保留物体的边缘轮廓。
顶帽:原图像与开运算图之差,用于分离比邻近点亮一些的斑块。
黑帽:闭运算与原图像之差,用于分离比邻近点暗一些的斑块。
opencv里有一个很好的函数getStructuringElement,我们只要往这个函数传相应的处理参数,就可以进行相应的操作了,使用起来非常方便。下面列举一下相应的操作宏定义。
#include<opencv2\opencv.hpp>
#include<opencv2\highgui\highgui.hpp>
using namespace std;
using namespace cv;
//高级形态学处理
int main()
{
Mat img = imread("lol1.jpg");
namedWindow("原始图", WINDOW_NORMAL);
imshow("原始图", img);
Mat out;
//获取自定义核
Mat element = getStructuringElement(MORPH_RECT, Size(15, 15)); //第一个参数MORPH_RECT表示矩形的卷积核,当然还可以选择椭圆形的、交叉型的
//高级形态学处理,调用这个函数就可以了,具体要选择哪种操作,就修改第三个参数就可以了。这里演示的是形态学梯度处理
morphologyEx(img, out, MORPH_GRADIENT, element);
namedWindow("形态学处理操作", WINDOW_NORMAL);
imshow("形态学处理操作", out);
waitKey(0);
}
4.模板匹配
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <iostream>
#include <stdio.h>
using namespace std;
using namespace cv;
int main()
{
Mat img, templ, result;
img = imread("nba.jpg");
templ = imread("76.png");
int result_cols = img.cols - templ.cols + 1;
int result_rows = img.rows - templ.rows + 1;
result.create(result_cols, result_rows, CV_32FC1);
matchTemplate(img, templ, result, CV_TM_SQDIFF_NORMED);//这里我们使用的匹配算法是标准平方差匹配 method=CV_TM_SQDIFF_NORMED,数值越小匹配度越好
normalize(result, result, 0, 1, NORM_MINMAX, -1, Mat());
double minVal = -1;
double maxVal;
Point minLoc;
Point maxLoc;
Point matchLoc;
cout << "匹配度:" << minVal << endl;
//result:结果矩阵;minVal:最小值;maxVal:最大值;minLoc:最小值坐标;maxLoc:最大值坐标
minMaxLoc(result, &minVal, &maxVal, &minLoc, &maxLoc, Mat());
cout << "匹配度:" << minVal << endl;
matchLoc = minLoc;
rectangle(img, matchLoc, Point(matchLoc.x + templ.cols, matchLoc.y + templ.rows), Scalar(0, 255, 0), 2, 8, 0);
imshow("img", img);
waitKey(0);
return 0;
}
匹配方法一共有6种
5.角点检测
Harris角点检测:Harris角点检测是一种直接基于灰度图的角点提取算法,稳定性高,尤其对L型角点(也就是直角)检测精度高。缺点也是明显的,就是运算速度慢。
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
Mat g_srcImage, g_srcImage1, g_grayImage;
int thresh = 30; //当前阈值
int max_thresh = 175; //最大阈值
void on_CornerHarris(int, void*);//回调函数
int main(int argc, char** argv)
{
g_srcImage = imread("1.jpg", 1);
if (!g_srcImage.data)
{
printf("读取图片错误! \n");
return -1;
}
imshow("原始图", g_srcImage);
g_srcImage1 = g_srcImage.clone();
//存留一张灰度图
cvtColor(g_srcImage1, g_grayImage, COLOR_BGR2GRAY);
//创建窗口和滚动条
namedWindow("角点检测", WINDOW_AUTOSIZE);
createTrackbar("阈值: ", "角点检测", &thresh, max_thresh, on_CornerHarris);
//调用一次回调函数,进行初始化
on_CornerHarris(0, 0);
waitKey(0);
return(0);
}
void on_CornerHarris(int, void*)
{
Mat dstImage;//目标图
Mat normImage;//归一化后的图
Mat scaledImage;//线性变换后的八位无符号整型的图
//置零当前需要显示的两幅图,即清除上一次调用此函数时他们的值
dstImage = Mat::zeros(g_srcImage.size(), CV_32FC1);
g_srcImage1 = g_srcImage.clone();
//进行角点检测
//第三个参数表示邻域大小,第四个参数表示Sobel算子孔径大小,第五个参数表示Harris参数
cornerHarris(g_grayImage, dstImage, 2, 3, 0.04, BORDER_DEFAULT);
// 归一化与转换
normalize(dstImage, normImage, 0, 255, NORM_MINMAX, CV_32FC1, Mat());
convertScaleAbs(normImage, scaledImage);//将归一化后的图线性变换成8位无符号整型
// 将检测到的,且符合阈值条件的角点绘制出来
for (int j = 0; j < normImage.rows; j++)
{
for (int i = 0; i < normImage.cols; i++)
{
//Mat::at<float>(j,i)获取像素值,并与阈值比较
if ((int)normImage.at<float>(j, i) > thresh + 80)
{
circle(g_srcImage1, Point(i, j), 5, Scalar(10, 10, 255), 2, 8, 0);
circle(scaledImage, Point(i, j), 5, Scalar(0, 10, 255), 2, 8, 0);
}
}
}
imshow("角点检测", g_srcImage1);
imshow("角点检测2", scaledImage);
}
Shi-Tomasi角点检测:除了上述的Harris角点检测方法,我们还可以采用Shi-Tomasi方法进行角点检测。Shi-Tomsi算法是Harris算法的加强版,性能当然也有相应的提高。
6.图像矫正
7.图像修复
8.图像配准
main.cpp
// https://www.cnblogs.com/skyfsm/p/7253208.html
9.图像拼接
#include <opencv2/highgui.hpp>
#include <opencv2/core.hpp>
#include <opencv2/features2d.hpp>
#include <opencv2/opencv.hpp>
#include <iostream>
#include <opencv2/xfeatures2d/nonfree.hpp>
using namespace cv;
using namespace std;
void OptimizeSeam(Mat& img1, Mat& trans, Mat& dst);
typedef struct
{
Point2f left_top;
Point2f left_bottom;
Point2f right_top;
Point2f right_bottom;
}four_corners_t;
four_corners_t corners;
void CalcCorners(const Mat& H, const Mat& src)
{
double v2[] = { 0, 0, 1 };//左上角
double v1[3];//变换后的坐标值
Mat V2 = Mat(3, 1, CV_64FC1, v2); //列向量
Mat V1 = Mat(3, 1, CV_64FC1, v1); //列向量
V1 = H * V2;
//左上角(0,0,1)
cout << "V2: " << V2 << endl;
cout << "V1: " << V1 << endl;
corners.left_top.x = v1[0] / v1[2];
corners.left_top.y = v1[1] / v1[2];
//左下角(0,src.rows,1)
v2[0] = 0;
v2[1] = src.rows;
v2[2] = 1;
V2 = Mat(3, 1, CV_64FC1, v2); //列向量
V1 = Mat(3, 1, CV_64FC1, v1); //列向量
V1 = H * V2;
corners.left_bottom.x = v1[0] / v1[2];
corners.left_bottom.y = v1[1] / v1[2];
//右上角(src.cols,0,1)
v2[0] = src.cols;
v2[1] = 0;
v2[2] = 1;
V2 = Mat(3, 1, CV_64FC1, v2); //列向量
V1 = Mat(3, 1, CV_64FC1, v1); //列向量
V1 = H * V2;
corners.right_top.x = v1[0] / v1[2];
corners.right_top.y = v1[1] / v1[2];
//右下角(src.cols,src.rows,1)
v2[0] = src.cols;
v2[1] = src.rows;
v2[2] = 1;
V2 = Mat(3, 1, CV_64FC1, v2); //列向量
V1 = Mat(3, 1, CV_64FC1, v1); //列向量
V1 = H * V2;
corners.right_bottom.x = v1[0] / v1[2];
corners.right_bottom.y = v1[1] / v1[2];
}
int main(int argc, char* argv[])
{
Mat image01 = imread(".\\src_pic\\1.jpg", 1); //右图
Mat image02 = imread(".\\src_pic\\2.jpg", 1); //左图
imshow("p1", image01);
imshow("p2", image02);
//灰度图转换
Mat image1, image2;
cvtColor(image01, image1, COLOR_RGB2GRAY);
cvtColor(image02, image2, COLOR_RGB2GRAY);
//提取特征点
//SurfFeatureDetector surfDetector(800); // 海塞矩阵阈值,在这里调整精度,值越大点越少,越精准
Ptr<xfeatures2d::SURF> surfDetector = xfeatures2d::SURF::create(800);
vector<KeyPoint> keyPoint1, keyPoint2;
surfDetector->detect(image1, keyPoint1);
surfDetector->detect(image2, keyPoint2);
//特征点描述,为下边的特征点匹配做准备
Mat imageDesc1, imageDesc2;
surfDetector->compute(image1, keyPoint1, imageDesc1);
surfDetector->compute(image2, keyPoint2, imageDesc2);
//获得匹配特征点,并提取最优配对
FlannBasedMatcher matcher;
vector<DMatch> matchePoints;
matcher.match(imageDesc1, imageDesc2, matchePoints, Mat());
cout << "**********************************************" << endl;
cout << "total match points: " << matchePoints.size() << endl;
// sort(matchePoints.begin(), matchePoints.end()); //特征点排序
Mat img_match;
drawMatches(image01, keyPoint1, image02, keyPoint2, matchePoints, img_match);
imshow("match points", img_match);
//获取排在前N个的最优匹配特征点
vector<Point2f> imagePoints1, imagePoints2;
for (int i = 0; i < matchePoints.size(); i++)
{
imagePoints1.push_back(keyPoint1[matchePoints[i].queryIdx].pt);
imagePoints2.push_back(keyPoint2[matchePoints[i].trainIdx].pt);
}
//获取图像1到图像2的投影映射矩阵 尺寸为3*3
Mat homo = findHomography(imagePoints1, imagePoints2, RANSAC);
也可以使用getPerspectiveTransform方法获得透视变换矩阵,不过要求只能有4个点,效果稍差
//Mat homo=getPerspectiveTransform(imagePoints1,imagePoints2);
cout << "变换矩阵为:\n" << homo << endl << endl; //输出映射矩阵
//计算配准图的四个顶点坐标
CalcCorners(homo, image01);
cout << "left_top:" << corners.left_top << endl;
cout << "left_bottom:" << corners.left_bottom << endl;
cout << "right_top:" << corners.right_top << endl;
cout << "right_bottom:" << corners.right_bottom << endl;
//图像配准
Mat imageTransform1, imageTransform2;
warpPerspective(image01, imageTransform1, homo, Size(MAX(corners.right_top.x, corners.right_bottom.x), image02.rows));
//warpPerspective(image01, imageTransform2, adjustMat*homo, Size(image02.cols*1.3, image02.rows*1.8));
imshow("直接经过透视矩阵变换", imageTransform1);
imwrite(".\\dst_pic\\trans1.jpg", imageTransform1);
//创建拼接后的图,需提前计算图的大小
int dst_width = imageTransform1.cols; //取最右点的长度为拼接图的长度
int dst_height = image02.rows;
Mat dst(dst_height, dst_width, CV_8UC3);
dst.setTo(0);
imageTransform1.copyTo(dst(Rect(0, 0, imageTransform1.cols, imageTransform1.rows)));
image02.copyTo(dst(Rect(0, 0, image02.cols, image02.rows)));
OptimizeSeam(image02, imageTransform1, dst);
imshow("dst", dst);
imwrite(".\\dst_pic\\dst.jpg", dst);
waitKey();
return 0;
}
//优化两图的连接处,使得拼接自然
void OptimizeSeam(Mat& img1, Mat& trans, Mat& dst)
{
int start = MIN(corners.left_top.x, corners.left_bottom.x);//开始位置,即重叠区域的左边界
double processWidth = img1.cols - start;//重叠区域的宽度
int rows = dst.rows;
int cols = img1.cols; //注意,是列数*通道数
double alpha = 1;//img1中像素的权重
for (int i = 0; i < rows; i++)
{
uchar* p = img1.ptr<uchar>(i); //获取第i行的首地址
uchar* t = trans.ptr<uchar>(i);
uchar* d = dst.ptr<uchar>(i);
for (int j = start; j < cols; j++)
{
if (t[j * 3] == 0 && t[j * 3 + 1] == 0 && t[j * 3 + 2] == 0)
{
alpha = 1;
}
else
{
alpha = (processWidth - (j - start)) / processWidth;
}
d[j * 3] = p[j * 3] * alpha + t[j * 3] * (1 - alpha);
d[j * 3 + 1] = p[j * 3 + 1] * alpha + t[j * 3 + 1] * (1 - alpha);
d[j * 3 + 2] = p[j * 3 + 2] * alpha + t[j * 3 + 2] * (1 - alpha);
}
}
}
10.全能扫描王
#include <iostream>
#include <opencv2\opencv.hpp>
#include <opencv2\imgproc\imgproc.hpp>
#include <algorithm>
using namespace std;
using namespace cv;
cv::Point2f center(0, 0);
bool sort_corners(std::vector<cv::Point2f>& corners)
{
std::vector<cv::Point2f> top, bot;
cv::Point2f tmp_pt;
std::vector<cv::Point2f> olddata = corners;
if (corners.size() != 4)
{
return false;
}
for (size_t i = 0; i < corners.size(); i++)
{
for (size_t j = i + 1; j < corners.size(); j++)
{
if (corners[i].y < corners[j].y)
{
tmp_pt = corners[i];
corners[i] = corners[j];
corners[j] = tmp_pt;
}
}
}
top.push_back(corners[0]);
top.push_back(corners[1]);
bot.push_back(corners[2]);
bot.push_back(corners[3]);
if (top.size() == 2 && bot.size() == 2) {
corners.clear();
cv::Point2f tl = top[0].x > top[1].x ? top[1] : top[0];
cv::Point2f tr = top[0].x > top[1].x ? top[0] : top[1];
cv::Point2f bl = bot[0].x > bot[1].x ? bot[1] : bot[0];
cv::Point2f br = bot[0].x > bot[1].x ? bot[0] : bot[1];
corners.push_back(tl);
corners.push_back(tr);
corners.push_back(br);
corners.push_back(bl);
return true;
}
else
{
corners = olddata;
return false;
}
}
cv::Point2f computeIntersect(cv::Vec4i a, cv::Vec4i b)
{
int x1 = a[0], y1 = a[1], x2 = a[2], y2 = a[3];
int x3 = b[0], y3 = b[1], x4 = b[2], y4 = b[3];
if (float d = ((float)(x1 - x2) * (y3 - y4)) - ((y1 - y2) * (x3 - x4)))
{
cv::Point2f pt;
pt.x = ((x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4)) / d;
pt.y = ((x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4)) / d;
return pt;
}
else
return cv::Point2f(-1, -1);
}
bool IsBadLine(int a, int b)
{
if (a * a + b * b < 100)
{
return true;
}
else
{
return false;
}
}
bool x_sort(const Point2f& m1, const Point2f& m2)
{
return m1.x < m2.x;
}
//确定四个点的中心线
void sortCorners(vector<Point2f>& corners,Point2f center)
{
vector<Point2f> top, bot;
vector<Point2f> backup = corners;
//sort(corners, x_sort); //注意先按x的大小给4个点排序
std::sort(corners.begin(), corners.end(), [](const cv::Point2f& p1, const cv::Point2f& p2) {
return p1.x < p2.x;
});
for (int i = 0; i < corners.size(); i++)
{
if (corners[i].y < center.y&& top.size() < 2) //这里的小于2是为了避免三个顶点都在top的情况
top.push_back(corners[i]);
else
bot.push_back(corners[i]);
}
corners.clear();
if (top.size() == 2 && bot.size() == 2)
{
cout << "log" << endl;
cv::Point2f tl = top[0].x > top[1].x ? top[1] : top[0];
cv::Point2f tr = top[0].x > top[1].x ? top[0] : top[1];
cv::Point2f bl = bot[0].x > bot[1].x ? bot[1] : bot[0];
cv::Point2f br = bot[0].x > bot[1].x ? bot[0] : bot[1];
corners.push_back(tl);
corners.push_back(tr);
corners.push_back(br);
corners.push_back(bl);
}
else
{
corners = backup;
}
}
int g_dst_hight; //最终图像的高度
int g_dst_width; //最终图像的宽度
void CalcDstSize(const vector<cv::Point2f>& corners)
{
int h1 = sqrt((corners[0].x - corners[3].x) * (corners[0].x - corners[3].x) + (corners[0].y - corners[3].y) * (corners[0].y - corners[3].y));
int h2 = sqrt((corners[1].x - corners[2].x) * (corners[1].x - corners[2].x) + (corners[1].y - corners[2].y) * (corners[1].y - corners[2].y));
g_dst_hight = MAX(h1, h2);
int w1 = sqrt((corners[0].x - corners[1].x) * (corners[0].x - corners[1].x) + (corners[0].y - corners[1].y) * (corners[0].y - corners[1].y));
int w2 = sqrt((corners[2].x - corners[3].x) * (corners[2].x - corners[3].x) + (corners[2].y - corners[3].y) * (corners[2].y - corners[3].y));
g_dst_width = MAX(w1, w2);
}
int main()
{
Mat src = imread("20.png");
imshow("src img", src);
Mat source = src.clone();
Mat bkup = src.clone();
Mat img = src.clone();
cvtColor(img, img, COLOR_RGB2GRAY); //二值化
imshow("gray", img);
//equalizeHist(img, img);
//imshow("equal", img);
GaussianBlur(img, img, Size(5, 5), 0, 0); //高斯滤波
//获取自定义核
Mat element = getStructuringElement(MORPH_RECT, Size(3, 3)); //第一个参数MORPH_RECT表示矩形的卷积核,当然还可以选择椭圆形的、交叉型的
//膨胀操作
dilate(img, img, element); //实现过程中发现,适当的膨胀很重要
imshow("dilate", img);
Canny(img, img, 30, 120, 3); //边缘提取
imshow("get contour", img);
vector<vector<Point> > contours;
vector<vector<Point> > f_contours;
std::vector<cv::Point> approx2;
//注意第5个参数为CV_RETR_EXTERNAL,只检索外框
findContours(img, f_contours, RETR_EXTERNAL, CHAIN_APPROX_NONE); //找轮廓
//求出面积最大的轮廓
int max_area = 0;
int index;
for (int i = 0; i < f_contours.size(); i++)
{
double tmparea = fabs(contourArea(f_contours[i]));
if (tmparea > max_area)
{
index = i;
max_area = tmparea;
}
}
contours.push_back(f_contours[index]);
cout << contours.size() << endl; //因为我写的是找出最外层轮廓,所以理论上只有一个轮廓
vector<Point> tmp = contours[0];
for (int line_type = 1; line_type <= 3; line_type++)
{
cout << "line_type: " << line_type << endl;
Mat black = img.clone();
black.setTo(0);
drawContours(black, contours, 0, Scalar(255), line_type); //注意线的厚度,不要选择太细的
imshow("show contour", black);
std::vector<Vec4i> lines;
std::vector<cv::Point2f> corners;
std::vector<cv::Point2f> approx;
int para = 10;
int flag = 0;
int round = 0;
for (; para < 300; para++)
{
cout << "round: " << ++round << endl;
lines.clear();
corners.clear();
approx.clear();
center = Point2f(0, 0);
cv::HoughLinesP(black, lines, 1, CV_PI / 180, para, 30, 10);
//过滤距离太近的直线
std::set<int> ErasePt;
for (int i = 0; i < lines.size(); i++)
{
for (int j = i + 1; j < lines.size(); j++)
{
if (IsBadLine(abs(lines[i][0] - lines[j][0]), abs(lines[i][1] - lines[j][1])) && (IsBadLine(abs(lines[i][2] - lines[j][2]), abs(lines[i][3] - lines[j][3]))))
{
ErasePt.insert(j);//将该坏线加入集合
}
}
}
int Num = lines.size();
while (Num != 0)
{
std::set<int>::iterator j = ErasePt.find(Num);
if (j != ErasePt.end())
{
lines.erase(lines.begin() + Num - 1);
}
Num--;
}
if (lines.size() != 4)
{
continue;
}
//计算直线的交点,保存在图像范围内的部分
for (int i = 0; i < lines.size(); i++)
{
for (int j = i + 1; j < lines.size(); j++)
{
cv::Point2f pt = computeIntersect(lines[i], lines[j]);
if (pt.x >= 0 && pt.y >= 0 && pt.x <= src.cols && pt.y <= src.rows) //保证交点在图像的范围之内
corners.push_back(pt);
}
}
if (corners.size() != 4)
{
continue;
}
#if 1
bool IsGoodPoints = true;
//保证点与点的距离足够大以排除错误点
for (int i = 0; i < corners.size(); i++)
{
for (int j = i + 1; j < corners.size(); j++)
{
int distance = sqrt((corners[i].x - corners[j].x) * (corners[i].x - corners[j].x) + (corners[i].y - corners[j].y) * (corners[i].y - corners[j].y));
if (distance < 5)
{
IsGoodPoints = false;
}
}
}
if (!IsGoodPoints) continue;
#endif
cv::approxPolyDP(cv::Mat(corners), approx, cv::arcLength(cv::Mat(corners), true) * 0.02, true);
if (lines.size() == 4 && corners.size() == 4 && approx.size() == 4)
{
flag = 1;
break;
}
}
// Get mass center
for (int i = 0; i < corners.size(); i++)
center += corners[i];
center *= (1. / corners.size());
if (flag)
{
cout << "we found it!" << endl;
cv::circle(bkup, corners[0], 3, CV_RGB(255, 0, 0), -1);
cv::circle(bkup, corners[1], 3, CV_RGB(0, 255, 0), -1);
cv::circle(bkup, corners[2], 3, CV_RGB(0, 0, 255), -1);
cv::circle(bkup, corners[3], 3, CV_RGB(255, 255, 255), -1);
cv::circle(bkup, center, 3, CV_RGB(255, 0, 255), -1);
imshow("backup", bkup);
cout << "corners size" << corners.size() << endl;
// cv::waitKey();
// bool sort_flag = sort_corners(corners);
// if (!sort_flag) cout << "fail to sort" << endl;
sortCorners(corners, center);
cout << "corners size" << corners.size() << endl;
cout << "tl:" << corners[0] << endl;
cout << "tr:" << corners[1] << endl;
cout << "br:" << corners[2] << endl;
cout << "bl:" << corners[3] << endl;
CalcDstSize(corners);
cv::Mat quad = cv::Mat::zeros(g_dst_hight, g_dst_width, CV_8UC3);
std::vector<cv::Point2f> quad_pts;
quad_pts.push_back(cv::Point2f(0, 0));
quad_pts.push_back(cv::Point2f(quad.cols, 0));
quad_pts.push_back(cv::Point2f(quad.cols, quad.rows));
quad_pts.push_back(cv::Point2f(0, quad.rows));
cv::Mat transmtx = cv::getPerspectiveTransform(corners, quad_pts);
cv::warpPerspective(source, quad, transmtx, quad.size());
imshow("find", bkup);
cv::imshow("quadrilateral", quad);
/*如果需要二值化就解掉注释把*/
/*
Mat local,gray;
cvtColor(quad, gray, COLOR_RGB2GRAY);
int blockSize = 25;
int constValue = 10;
adaptiveThreshold(gray, local, 255, CV_ADAPTIVE_THRESH_MEAN_C, CV_THRESH_BINARY, blockSize, constValue);
imshow("二值化", local);
*/
cv::waitKey();
return 0;
}
}
cout << "can not transform!" << endl;
waitKey();
}
11.特征检测和特征匹配
- SURF
特征检测的视觉不变性是一个非常重要的概念。但是要解决尺度不变性问题,难度相当大。为解决这一问题,计算机视觉界引入了尺度不变特征的概念。 它的理念是,不仅在任何尺度下拍摄的物体都能检测到一致的关键点,而且每个被检测的特征点都对应一个尺度因子。 理想情况下,对于两幅图像中不同尺度的的同一个物体点, 计算得到的两个尺度因子之间的比率应该等于图像尺度的比率。近几年,人们提出了多种尺度不变特征,本节介绍其中的一种:SURF特征。 SURF全称为“加速稳健特征”(Speeded Up Robust Feature),我们将会看到,它们不仅是尺度不变特征,而且是具有较高计算效率的特征
如果我们拿着这样子的匹配结果去实现图像拼接或者物体追踪,效果肯定是极差的。所以我们需要进一步筛选匹配点,来获取优秀的匹配点,这就是所谓的“去粗取精”。这里我们采用了Lowe’s算法来进一步获取优秀匹配点。为了排除因为图像遮挡和背景混乱而产生的无匹配关系的关键点,SIFT的作者Lowe提出了比较最近邻距离与次近邻距离的SIFT匹配方式:取一幅图像中的一个SIFT关键点,并找出其与另一幅图像中欧式距离最近的前两个关键点,在这两个关键点中,如果最近的距离除以次近的距离得到的比率ratio少于某个阈值T,则接受这一对匹配点。因为对于错误匹配,由于特征空间的高维性,相似的距离可能有大量其他的错误匹配,从而它的ratio值比较高。显然降低这个比例阈值T,SIFT匹配点数目会减少,但更加稳定,反之亦然。Lowe推荐ratio的阈值为0.8,但作者对大量任意存在尺度、旋转和亮度变化的两幅图片进行匹配,结果表明ratio取值在0. 4~0. 6 之间最佳,小于0. 4的很少有匹配点,大于0. 6的则存在大量错误匹配点,所以建议ratio的取值原则如下:
ratio=0. 4:对于准确度要求高的匹配;
ratio=0. 6:对于匹配点数目要求比较多的匹配;
ratio=0. 5:一般情况下。
#include "highgui/highgui.hpp"
#include <opencv2/opencv.hpp>
#include <iostream>
#include <opencv2/xfeatures2d/nonfree.hpp>
using namespace cv;
using namespace std;
int main()
{
Mat image01 = imread(".\\src_pic\\2.jpg", 1); //右图
Mat image02 = imread(".\\src_pic\\1.jpg", 1); //左图
namedWindow("p2", 0);
namedWindow("p1", 0);
imshow("p2", image01);
imshow("p1", image02);
//灰度图转换
Mat image1, image2;
cvtColor(image01, image1, COLOR_RGB2GRAY);
cvtColor(image02, image2, COLOR_RGB2GRAY);
//提取特征点
//SurfFeatureDetector surfDetector(800); // 海塞矩阵阈值,在这里调整精度,值越大点越少,越精准
Ptr<xfeatures2d::SURF> surfDetector = xfeatures2d::SURF::create(2000);
vector<KeyPoint> keyPoint1, keyPoint2;
surfDetector->detect(image1, keyPoint1);
surfDetector->detect(image2, keyPoint2);
//特征点描述,为下边的特征点匹配做准备
Mat imageDesc1, imageDesc2;
surfDetector->compute(image1, keyPoint1, imageDesc1);
surfDetector->compute(image2, keyPoint2, imageDesc2);
//获得匹配特征点,并提取最优配对
FlannBasedMatcher matcher;
vector<vector<DMatch>> matchePoints;
vector<DMatch> GoodMatchePoints;
vector<Mat> train_desc(1, imageDesc1);
matcher.add(train_desc);
matcher.train();
matcher.knnMatch(imageDesc2, matchePoints, 2);
cout << "total match points: " << matchePoints.size() << endl;
//Lowe's algorithm,获取优秀匹配点
for (int i = 0; i < matchePoints.size(); i++)
{
if (matchePoints[i][0].distance < 0.6 * matchePoints[i][1].distance)
{
GoodMatchePoints.push_back(matchePoints[i][0]);
}
}
Mat first_match;
drawMatches(image02, keyPoint2, image01, keyPoint1, GoodMatchePoints, first_match);
imshow("first_match ", first_match);
waitKey();
return 0;
}
- SIFT
SURF算法是SIFT算法的加速版, 而SIFT(尺度不变特征转换, ScaleInvariant Feature Transform) 是另一种著名的尺度不变特征检测法。我们知道,SURF相对于SIFT而言,特征点检测的速度有着极大的提升,所以在一些实时视频流物体匹配上有着很强的应用。而SIFT因为其巨大的特征计算量而使得特征点提取的过程异常花费时间,所以在一些注重速度的场合难有应用场景。但是SIFT相对于SURF的优点就是,由于SIFT基于浮点内核计算特征点,因此通常认为, SIFT算法检测的特征在空间和尺度上定位更加精确,所以在要求匹配极度精准且不考虑匹配速度的场合可以考虑使用SIFT算法 - ORB
ORB是ORiented Brief的简称,是brief算法的改进版。ORB算法比SIFT算法快100倍,比SURF算法快10倍。在计算机视觉领域有种说法,ORB算法的综合性能在各种测评里较其他特征提取算法是最好的。ORB算法是brief算法的改进,那么我们先说一下brief算法有什么去缺点。BRIEF的优点在于其速度,其缺点是:不具备旋转不变性,对噪声敏感,不具备尺度不变性
而ORB算法就是试图解决上述缺点中1和2提出的一种新概念。值得注意的是,ORB没有解决尺度不变性。 - FAST
FAST(加速分割测试获得特征, Features from Accelerated Segment Test) 。这种算子专门用来快速检测兴趣点, 只需要对比几个像素,就可以判断是否为关键点。跟Harris检测器的情况一样, FAST算法源于对构成角点的定义。FAST对角点的定义基于候选特征点周围的图像强度值。 以某个点为中心作一个圆, 根据圆上的像素值判断该点是否为关键点。 如果存在这样一段圆弧, 它的连续长度超过周长的3/4, 并且它上面所有像素的强度值都与圆心的强度值明显不同(全部更黑或更亮),那么就认定这是一个关键点。
用这个算法检测兴趣点的速度非常快, 因此十分适合需要优先考虑速度的应用。 这些应用包括实时视觉跟踪、 目标识别等, 它们需要在实
时视频流中跟踪或匹配多个点。我们使用FastFeatureDetector 进行特征点提取,因为opencv没有提供fast专用的描述子提取器,所以我们借用SiftDescriptorExtractor 来实现描述子的提取。