一.问题描述
拍摄或者扫描图像不是规则的矩形,会对后期处理产生不好的影响,需要通过透视变换矫正得到正确的形状。
适用场景:对于机器学习、深度学习中的一些算法,算法本身很好,但对源图的要求却很高,有些图像摆放不正,若通过透视矫正,可以提高检测的准确率。
就是把左边的图像进行透视矫正变成右边的图像。
二.用到的API
透视变换warpPerspective
PS:书《Opencv3 编程入门》那本书中,是仿射变换,用于图像的旋转等等,一开始我还搞混了。
关于透视变换warpPerspective的介绍参考博客:
https://blog.csdn.net/i_chaoren/article/details/78324184
三.解决的思路
二值分割+形态学方法+Hough直线检测+透视变换
四.程序演示
#include <opencv2/opencv.hpp>
#include <math.h>
#include <iostream>
using namespace cv;
using namespace std;
int main(int argc,char** argv)
{
Mat srcImage = imread("E:\\pictures\\47.jpg");
if (srcImage.empty())
{
cout << "图像读取错误!" << endl;
return -1;
}
namedWindow("【1】原图", WINDOW_AUTOSIZE);
imshow("【1】原图", srcImage);
//二值处理
Mat srcImage_gray, srcImage_binary, dstImage;
cvtColor(srcImage, srcImage_gray, COLOR_BGR2GRAY);
threshold(srcImage_gray, srcImage_binary, 0, 255, THRESH_BINARY_INV | THRESH_OTSU);
//THRESH_BINARY_INV 对象设置为黑色,背景为白色
//THRESH_BINARY 对象设置为白色,背景为黑色
imshow("【2】二值化后", srcImage_binary);
//形态学操作
//目的:去除掉二值化后的噪点
Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1));
morphologyEx(srcImage_binary, dstImage, MORPH_CLOSE, kernel, Point(-1, -1), 3);//闭操作可以去掉图像中小点点,噪点
imshow("【3】形态学操作后", dstImage);
//发现轮廓
bitwise_not(dstImage, dstImage, Mat());
vector<vector<Point>> contours;
vector<Vec4i> hireachy;
findContours(dstImage, contours, hireachy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point());
//绘制轮廓
int width = srcImage.cols;
int height = srcImage.rows;
Mat drawImage = Mat::zeros(srcImage.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("【4】绘制轮廓后", drawImage);
//霍夫直线检测
vector<Vec4i> lines;
Mat contoursImage;
int minLineLength = min(width * 0.5, height * 0.5); //表示最低线段长度
cvtColor(drawImage, contoursImage, COLOR_BGR2GRAY);
HoughLinesP(contoursImage, lines, 1, CV_PI / 180.0, minLineLength, minLineLength, 0);
Mat LinesImage = Mat::zeros(srcImage.size(), CV_8UC3);
for (size_t t = 0; t < lines.size(); t++)
{
Vec4i In = lines[t];
line(LinesImage, Point(In[0], In[1]), Point(In[2], In[3]), Scalar(0, 0, 255), 2, 8, 0);
}
cout << "线段的个数:" << lines.size() << endl;//这里寻找的线段多了无所谓,重合了后仍是那条线段
imshow("【5】霍夫直线检测后", LinesImage);
//寻找与定位上下左右四条直线
int height_difference = 0; //定义高度差
Vec4i topLine, bottomLine;
Vec4i leftLine, rightLine;
for (int i = 0; i < lines.size(); i++)
{
Vec4i In = lines[i];
height_difference = abs(In[3] - In[1]); //取绝对值
if (In[3] < height / 2.0 && In[1] < height / 2.0 && height_difference < minLineLength - 1)
{
topLine = lines[i];
}
if (In[3] > height / 2.0 && In[1] > height / 2.0 && height_difference < minLineLength - 1)
{
bottomLine = lines[i];
}
if (In[0] < width / 2.0 && In[2] < width / 2.0)
{
leftLine = lines[i];
}
if (In[0] > width / 2.0 && In[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 << "bottomLine line:p1(x,y) = " << bottomLine[0] << "," << bottomLine[1] << " ;p2(x, y) = " << bottomLine[2] << "," << bottomLine[3] << endl;
cout << "leftLine line:p1(x,y) = " << leftLine[0] << "," << leftLine[1] << " ;p2(x, y) = " << leftLine[2] << "," << leftLine[3] << endl;
cout << "rightLine line:p1(x,y) = " << rightLine[0] << "," << rightLine[1] << " ;p2(x, y) = " << rightLine[2] << "," << rightLine[3] << endl;
//上一步操作寻找到了轮廓的四条直线
//下面求出四条直线相交的四个顶点,左上角,右上角,左下角,右下角
//这一步求出四条直线的方程,通过 y=kx+c
//y=k1x+c1
float k1, c1;
k1 = float(topLine[3] - topLine[1]) / float(topLine[2] - topLine[0]);
c1 = topLine[1] - k1 * topLine[0];
//y = k2x + c2
float k2, c2;
k2 = float(bottomLine[3] - bottomLine[1]) / float(bottomLine[2] - bottomLine[0]);
c2 = bottomLine[1] - k2 * bottomLine[0];
//y = k3x + c3
float k3, c3;
k3 = float(leftLine[3] - leftLine[1]) / float(leftLine[2] - leftLine[0]);
c3 = leftLine[1] - k3 * leftLine[0];
//y = k4x + c4
float k4, c4;
k4 = float(rightLine[3] - rightLine[1]) / float(rightLine[2] - rightLine[0]);
c4 = rightLine[1] - k4 * rightLine[0];
//上一步操作求出了上下左右四条直线的方程
//这一步求出四条直线相交的四个交点,左上角,右上角,左下角,右下角
//理论基础:两条直线求交点:y=k1x+c1,y = k3x + c3
// k1x+c1 = k3x + c3 得出:x = (c1-c3) / (k3-k1),
// 算出x后代入两条中任一直线求出y的值,若匹配的点不对,就换另一条直线
Point p1;//左上角:topLine与leftLine交点
p1.x = static_cast<int>((c1 - c3) / (k3 - k1));
p1.y = static_cast<int>(k1 * p1.x + c1);
Point p2;//右上角:topLine与rightLine交点
p2.x = static_cast<int>((c1 - c4) / (k4 - k1));
p2.y = static_cast<int>(k1 * p2.x + c1);
Point p3;//左下角:leftLine与bottomLine交点
p3.x = static_cast<int>((c2 - c3) / (k3 - k2));
p3.y = static_cast<int>(k2 * p3.x + c2);
Point p4;//右下角:rightLine与bottomLine交点
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);
imshow("【6】四个顶点", LinesImage);
//进行透视变换
vector<Point2f> srcImage_corners(4);
//这里注意透视变换的点坐标的顺序,是从左上角开始,以顺时针的顺序,而之前p1是以左上角右上角左下角右下角的顺序
srcImage_corners[0] = p1;
srcImage_corners[1] = p2;
srcImage_corners[2] = p4;
srcImage_corners[3] = p3;
vector<Point2f> dstImage_corners(4);//顺序与上面对应
dstImage_corners[0] = Point(0,0);
dstImage_corners[1] = Point(width,0);
dstImage_corners[2] = Point(width, height);
dstImage_corners[3] = Point(0, height);
//获取透视变换矩阵
Mat resultImage;
Mat warpmatrix = getPerspectiveTransform(srcImage_corners, dstImage_corners);
warpPerspective(srcImage, resultImage, warpmatrix, resultImage.size(), INTER_LINEAR);
imshow("【7】效果图", resultImage);
waitKey(0);
return 0;
}
输出结果: