对于倾斜的图片通过矫正可以得到水平的图片。一般有如下几种基于opencv的组合方式进行图片矫正。
- 傅里叶变换 + 霍夫变换+ 直线 + 角度 + 旋转
- 边缘检测 + 霍夫变换 + 直线+角度 + 旋转
- 四点透视 + 角度 + 旋转
- 检测矩形轮廓 + 角度 + 旋转
傅里叶 + 霍夫变换
#include
#include
#include
#include
using namespace cv;
using namespace std;
// 二值化阈值
#define GRAY_THRESH 150
// 直线上点的个数
#define HOUGH_VOTE 50
int main(int argc, char **argv)
{
//Read a single-channel image
const char* filename = "31.png";
Mat srcImg = imread(filename, CV_LOAD_IMAGE_GRAYSCALE);
if (srcImg.empty())
return -1;
imshow("source", srcImg);
Point center(srcImg.cols / 2, srcImg.rows / 2);
//Expand image to an optimal size, for faster processing speed
//Set widths of borders in four directions
//If borderType==BORDER_CONSTANT, fill the borders with (0,0,0)
Mat padded;
int opWidth = getOptimalDFTSize(srcImg.rows);
int opHeight = getOptimalDFTSize(srcImg.cols);
copyMakeBorder(srcImg, padded, 0, opWidth - srcImg.rows, 0, opHeight - srcImg.cols, BORDER_CONSTANT, Scalar::all(0));
Mat planes[] = { Mat_(padded), Mat::zeros(padded.size(), CV_32F) };
Mat comImg;
//Merge into a double-channel image
merge(planes, 2, comImg);
//Use the same image as input and output,
//so that the results can fit in Mat well
dft(comImg, comImg);
//Compute the magnitude
//planes[0]=Re(DFT(I)), planes[1]=Im(DFT(I))
//magnitude=sqrt(Re^2+Im^2)
split(comImg, planes);
magnitude(planes[0], planes[1], planes[0]);
//Switch to logarithmic scale, for better visual results
//M2=log(1+M1)
Mat magMat = planes[0];
magMat += Scalar::all(1);
log(magMat, magMat);
//Crop the spectrum
//Width and height of magMat should be even, so that they can be divided by 2
//-2 is 11111110 in binary system, operator & make sure width and height are always even
magMat = magMat(Rect(0, 0, magMat.cols & -2, magMat.rows & -2));
//Rearrange the quadrants of Fourier image,
//so that the origin is at the center of image,
//and move the high frequency to the corners
int cx = magMat.cols / 2;
int cy = magMat.rows / 2;
Mat q0(magMat, Rect(0, 0, cx, cy));
Mat q1(magMat, Rect(0, cy, cx, cy));
Mat q2(magMat, Rect(cx, cy, cx, cy));
Mat q3(magMat, Rect(cx, 0, cx, cy));
Mat tmp;
q0.copyTo(tmp);
q2.copyTo(q0);
tmp.copyTo(q2);
q1.copyTo(tmp);
q3.copyTo(q1);
tmp.copyTo(q3);
//Normalize the magnitude to [0,1], then to[0,255]
normalize(magMat, magMat, 0, 1, CV_MINMAX);
Mat magImg(magMat.size(), CV_8UC1);
magMat.convertTo(magImg, CV_8UC1, 255, 0);
imshow("magnitude", magImg);
//imwrite("imageText_mag.jpg",magImg);
//Turn into binary image
threshold(magImg, magImg, GRAY_THRESH, 255, CV_THRESH_BINARY);
imshow("mag_binary", magImg);
//imwrite("imageText_bin.jpg",magImg);
//Find lines with Hough Transformation
vector lines;
float pi180 = (float)CV_PI / 180;
Mat linImg(magImg.size(), CV_8UC3);
HoughLines(magImg, lines, 1, pi180, HOUGH_VOTE, 0, 0);
int numLines = lines.size();
for (int l = 0; l
如果检测不出来,通过修改两个参数进行调节:
GRAY_THRESH 150 :二值化的阈值
HOUGH_VOTE 50 : 霍夫变换时每条线上的最少点个数
边缘检测 + 霍夫变换
#include "opencv2/imgproc.hpp"
#include "opencv2/highgui.hpp"
#include
using namespace cv;
using namespace std;
// 直线上点的个数
#define HOUGH_VOTE 50
//度数转换
double DegreeTrans(double theta)
{
double res = theta / CV_PI * 180;
return res;
}
//逆时针旋转图像degree角度(原尺寸)
void rotateImage(Mat src, Mat& img_rotate, double degree)
{
//旋转中心为图像中心
Point2f center;
center.x = float(src.cols / 2.0);
center.y = float(src.rows / 2.0);
int length = 0;
length = sqrt(src.cols*src.cols + src.rows*src.rows);
//计算二维旋转的仿射变换矩阵
Mat M = getRotationMatrix2D(center, degree, 1);
warpAffine(src, img_rotate, M, Size(length, length), 1, 0, Scalar(255, 255, 255));//仿射变换,背景色填充为白色
}
//通过霍夫变换计算角度
double CalcDegree(const Mat &srcImage, Mat &dst)
{
Mat midImage, dstImage;
Canny(srcImage, midImage, 50, 200, 3);
cvtColor(midImage, dstImage, CV_GRAY2BGR);
//通过霍夫变换检测直线
vector lines;
HoughLines(midImage, lines, 1, CV_PI / 180, HOUGH_VOTE);//第5个参数就是阈值,阈值越大,检测精度越高
//cout << lines.size() << endl;
//由于图像不同,阈值不好设定,因为阈值设定过高导致无法检测直线,阈值过低直线太多,速度很慢
//所以根据阈值由大到小设置了三个阈值,如果经过大量试验后,可以固定一个适合的阈值。
float sum = 0;
//依次画出每条线段
for (size_t i = 0; i < lines.size(); i++)
{
float rho = lines[i][0];
float theta = lines[i][1];
Point pt1, pt2;
//cout << theta << endl;
double a = cos(theta), b = sin(theta);
double x0 = a*rho, y0 = b*rho;
pt1.x = cvRound(x0 + 1000 * (-b));
pt1.y = cvRound(y0 + 1000 * (a));
pt2.x = cvRound(x0 - 1000 * (-b));
pt2.y = cvRound(y0 - 1000 * (a));
//只选角度最小的作为旋转角度
sum += theta;
line(dstImage, pt1, pt2, Scalar(55, 100, 195), 1, LINE_AA); //Scalar函数用于调节线段颜色
imshow("直线探测效果图", dstImage);
}
float average = sum / lines.size(); //对所有角度求平均,这样做旋转效果会更好
cout << "average theta:" << average << endl;
double angle = DegreeTrans(average) - 90;
rotateImage(dstImage, dst, angle);
//imshow("直线探测效果图2", dstImage);
return angle;
}
void ImageRecify(const char* pInFileName, const char* pOutFileName)
{
double degree;
Mat src = imread(pInFileName);
imshow("原始图", src);
Mat dst;
//倾斜角度矫正
degree = CalcDegree(src, dst);
rotateImage(src, dst, degree);
cout << "angle:" << degree << endl;
imshow("旋转调整后", dst);
Mat resulyImage = dst(Rect(0, 0, dst.cols, 500)); //根据先验知识,估计好文本的长宽,再裁剪下来
imshow("裁剪之后", resulyImage);
imwrite("recified.jpg", resulyImage);
}
int main()
{
ImageRecify("31.png", "FinalImage.jpg");
waitKey();
return 0;
}
矩形轮廓 + 角度 + 旋转
#include "opencv2/imgproc.hpp"
#include "opencv2/highgui.hpp"
#include
using namespace cv;
using namespace std;
#include
bool x_sort(const Point2f & m1, const Point2f & m2)
{
return m1.x < m2.x;
}
//第一个参数:输入图片名称;第二个参数:输出图片名称
void GetContoursPic(const char* pSrcFileName, const char* pDstFileName)
{
Mat srcImg = imread(pSrcFileName);
imshow("原始图", srcImg);
Mat gray, binImg;
//灰度化
cvtColor(srcImg, gray, COLOR_RGB2GRAY);
imshow("灰度图", gray);
//二值化
threshold(gray, binImg, 150, 200, CV_THRESH_BINARY);
imshow("二值化", binImg);
vector contours;
vector > f_contours;
//注意第5个参数为CV_RETR_EXTERNAL,只检索外框
findContours(binImg, f_contours, CV_RETR_EXTERNAL, CV_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 = f_contours[index];
CvBox2D rect = minAreaRect(Mat(contours));
float angle = rect.angle;
cout << "before angle : " << angle << endl;
if (angle < -45)
angle = (90 + angle);
else
angle = -angle;
cout << "after angle : " << angle << endl;
//新建一个感兴趣的区域图,大小跟原图一样大
Mat RoiSrcImg(srcImg.rows, srcImg.cols, CV_8UC3); //注意这里必须选CV_8UC3
RoiSrcImg.setTo(0); //颜色都设置为黑色
//imshow("新建的ROI", RoiSrcImg);
//对得到的轮廓填充一下
drawContours(binImg, f_contours, 0, Scalar(255), CV_FILLED);
//抠图到RoiSrcImg
srcImg.copyTo(RoiSrcImg, gray);
//再显示一下看看,除了感兴趣的区域,其他部分都是黑色的了
namedWindow("RoiSrcImg", 1);
imshow("RoiSrcImg", RoiSrcImg);
//创建一个旋转后的图像
Mat RatationedImg(RoiSrcImg.rows, RoiSrcImg.cols, CV_8UC1);
RatationedImg.setTo(0);
//对RoiSrcImg进行旋转
Point2f center = rect.center; //中心点
Mat M2 = getRotationMatrix2D(center, angle, 1);//计算旋转加缩放的变换矩阵
warpAffine(RoiSrcImg, RatationedImg, M2, RoiSrcImg.size(), 1, 0, Scalar(0));//仿射变换
imshow("旋转之后", RatationedImg);
}
void main()
{
GetContoursPic("34.png", "FinalImage.jpg");
waitKey();
}
参考:
OpenCV实现基于傅里叶变换的旋转文本校正
OpenCV-Python的文本透视矫正与水平矫正
penCV探索之路(十六):图像矫正技术深入探讨
OpenCV探索之路(二十二):制作一个类“全能扫描王”的简易扫描软件
OpenCV—python 图像矫正(基于傅里叶变换—基于透视变换
python+opencv实现基于傅里叶变换的旋转文本校正
Python+OpenCV实现旋转文本校正