目录
系统环境 Windows 10 64 位 + OpenCV 3.4.1 64 位
1、常见图像旋转矫正方法
常见的图像旋转矫正方式有:基于图像边缘轮廓的旋转矫正和基于傅里叶变换以及霍夫直线检测的旋转矫正两种方法。
1.1 基于图像边缘轮廓的旋转矫正
1)图像灰度化
2)阈值二值化
3)检测轮廓
4)提取轮廓的包围矩阵(图像前景掩膜)
5)通过提取的包围矩阵获取偏转角度
6)利用仿射变换对图像进行偏转
具体实现参考:OpenCV探索之路(十六):图像矫正技术深入探讨
1.2 基于傅里叶变换以及霍夫直线检测的旋转矫正
1)利用DTF变换计算图像的频谱图
2)频谱中心移动
3)对移动后的频谱图进行二值分割
4)利用霍夫直线检测计算图像倾斜角度
5)利用仿射变换对图像进行旋转矫正
具体实现参考:OpenCV实现基于傅里叶变换的旋转文本校正
傅里叶变换的原理解析参考:傅里叶分析之掐死教程(完整版)和OpenCV图像的傅里叶变换-(补番)
2、基于Hu距图像旋转矫正
除了以上两种方式可以实现图像旋转矫正外,还可以利用HU距旋转不变性对图像进行旋转矫正。
2.1 Hu旋转不变性
图片经过任意角度旋转、任意比例缩放Hu矩都保持不变,即图像Hu 矩的平移、旋转不变性。Hu旋转不变性详情参考:图像不变性特征—hu矩、图像特征_图像矩(Hu矩)。七个不变矩由二阶和三阶中心矩的线性组合构成,具体表达式如下:
在实际应用中,在对图片中物体进行处理时,只有 M1 和 M2 体现良好的不变性,其余的几个不变矩产生的误差比较大。因此,认为只有基于二阶矩的不变矩对二维物体的描述具有良好的旋转、缩放和平移不变性(M1和 M2 由二阶矩组成)。
图像的二阶矩表示图像的主轴,二阶矩值的大小可以确定图像的长轴和短轴,以此来计算轴的方向角度。最后,用主轴相对于法线轴的逆时针旋转角度来近似图像的旋转方向。
2.2 实现步骤
2.2.1 分别计算图像二阶距
其中,I(x,y)表示图像在点(x,y)处的灰度值,cx,cy表示图像的质心坐标。
2.2.2 利用得到的二阶距计算图像偏转角度
2.2.3 利用仿射变换对图像进行旋转矫正
2.4 程序实现
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/opencv.hpp>
#include<iostream>
using namespace std;
using namespace cv;
//计算图像质心坐标
bool calCentroid(Mat srcImg, int width, int height, int *x, int *y)
{
if (srcImg.empty())
{
return false;
}
int i, j;
long long m00 = 0, m10 = 0, m01 = 0;
for (i = 0; i < height; i++)
{
for (j = 0; j < width; j++)
{
uchar tmp = srcImg.at<uchar>(i, j);
m00 += tmp;
m10 += tmp * j;
m01 += tmp * i;
}
}
if (m00 != 0)
{
*x = m10 / m00;
*y = m01 / m00;
//cout <<"x:"<<*x<< endl;
//cout << "y:" << *y << endl;
}
else
{
*x = 0;
*y = 0;
return false;
}
return true;
}
//计算偏转角度
double getAngle(Mat srcImg)
{
double angle = 0;
long long m00 = 0, m11 = 0, m12 = 0, m22 = 0;
int Rho11 = 0, Rho12 = 0, Rho22 = 0;
int gx = 0, gy = 0;
//计算图像偏转角度
if (calCentroid(srcImg, srcImg.cols, srcImg.rows, &gx, &gy) == true)//计算图像质心
{
//cout << "gx:" << gx << endl;
//cout << "gy:" << gy << endl;
//归一化二阶中心矩
for (int i = 0; i < srcImg.rows; i++)
{
for (int j = 0; j < srcImg.cols; j++)
{
uchar tmp = srcImg.at<uchar>(i, j);
m00 = m00 + tmp;
m12 = m12 + (i - gy)*(j - gx)*tmp;
m11 = m11 + (i - gy)*(i - gy)*tmp;
m22 = m22 + (j - gx)*(j - gx)*tmp;
}
}
Rho11 = m11 / m00;
Rho12 = m12 / m00;
Rho22 = m22 / m00;
}
float tempx = 0, tempy = 0;
//计算偏转角度
if (Rho11 > Rho22)
{
tempy = Rho11 - Rho22 + sqrt((float)((Rho11 - Rho22)*(Rho11 - Rho22) + 4 * Rho12*Rho12));
tempx = (float)(-2 * Rho12);
angle = atan(tempy / tempx) * 180 / CV_PI;
}
else
{
tempy = (float)(-2 * Rho12);
tempx = Rho22 - Rho11 + sqrt((float)((Rho22 - Rho11)*(Rho22 - Rho11) + 4 * Rho12*Rho12));
angle = atan(tempy / tempx) * 180 / CV_PI;
}
//偏转角度范围:-45=< angle <=45
if (angle >= -45 && angle <= 45)
{
angle = angle;
}
else if (angle < -45)
{
angle = -(90 + angle);
}
else if (angle > 45)
{
angle = 90 - angle;
}
return angle;
}
//对图像进行旋转矫正
bool ContoursCorrection(Mat srcImg, Mat &dstImage)
{
if (srcImg.empty())
{
return false;
}
Mat gray, binImg;
//灰度化
cvtColor(srcImg, gray, COLOR_RGB2GRAY);
imshow("灰度图", gray);
//二值化
threshold(gray, binImg, 100, 200, CV_THRESH_BINARY);
imshow("二值化", binImg);
vector<vector<Point> > contours;
vector<Rect> boundRect(contours.size());
//注意第5个参数为CV_RETR_EXTERNAL,只检索外框
findContours(binImg, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE); //找轮廓
cout << contours.size() << endl;
for (int i = 0; i < contours.size(); i++)
{
//需要获取的坐标
CvPoint2D32f rectpoint[4];
CvBox2D rect = minAreaRect(Mat(contours[i]));
cvBoxPoints(rect, rectpoint); //获取4个顶点坐标
//与水平线的角度
int line1 = sqrt((rectpoint[1].y - rectpoint[0].y)*(rectpoint[1].y - rectpoint[0].y) + (rectpoint[1].x - rectpoint[0].x)*(rectpoint[1].x - rectpoint[0].x));
int line2 = sqrt((rectpoint[3].y - rectpoint[0].y)*(rectpoint[3].y - rectpoint[0].y) + (rectpoint[3].x - rectpoint[0].x)*(rectpoint[3].x - rectpoint[0].x));
//面积太小的直接pass
if (line1 * line2 < 600)
{
continue;
}
//新建一个感兴趣的区域图,大小跟原图一样大
Mat RoiSrcImg(srcImg.rows, srcImg.cols, CV_8UC3); //注意这里必须选CV_8UC3
RoiSrcImg.setTo(0); //颜色都设置为黑色
//imshow("新建的ROI", RoiSrcImg);
//对得到的轮廓填充一下
drawContours(binImg, contours, -1, Scalar(255), CV_FILLED);
//抠图到RoiSrcImg
srcImg.copyTo(RoiSrcImg, binImg);
////计算偏转角度
double angle = getAngle(binImg);
cout <<"angle:"<<angle<< endl;
//为了让正方形横着放,所以旋转角度是不一样的。竖放的,给他加90度,翻过来
if (line1 > line2)
{
angle = 90 + angle;
}
//再显示一下看看,除了感兴趣的区域,其他部分都是黑色的了
namedWindow("RoiSrcImg", 1);
imshow("RoiSrcImg", RoiSrcImg);
//对RoiSrcImg进行旋转
Point2f center = rect.center; //中心点
Mat M2 = getRotationMatrix2D(center, angle, 1);//计算旋转加缩放的变换矩阵
warpAffine(RoiSrcImg, dstImage, M2, RoiSrcImg.size(), 1, 0, Scalar(0));//仿射变换
imshow("旋转之后", dstImage);
}
return true;
}
int main(int argc, char *argv[])
{
//读取输入图像
Mat input = imread("src.png", IMREAD_COLOR);
Mat out(input.size(),CV_8UC3,Scalar(0,0,0));
ContoursCorrection(input, out);
waitKey(0);
system("pause");
return 0;
}
2.5 旋转矫正效果验证
从上图中可以发现,旋转矫正后图像由倾斜转换成为水平。
公众号《Slater》回复“机器学习”即可免费获取一份机器学习资料。