OpenCV图片校正
- 背景
- 几种校正方法
- 1.傅里叶变换 + 霍夫变换+ 直线 + 角度 + 旋转
- 3.四点透视 + 角度 + 旋转
- 4.检测矩形轮廓 + 角度 + 旋转
- 参考
背景
遇到偏的图片想要校正成水平或者垂直的。
几种校正方法
对于倾斜的图片通过矫正可以得到水平的图片。一般有如下几种基于opencv的组合方式进行图片矫正。
- 1、傅里叶变换 + 霍夫变换+ 直线 + 角度 + 旋转
- 2、边缘检测 + 霍夫变换 + 直线+角度 + 旋转
- 3、四点透视 + 角度 + 旋转
- 4、检测矩形轮廓 + 角度 + 旋转
1.傅里叶变换 + 霍夫变换+ 直线 + 角度 + 旋转
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <iostream>using namespace cv;
using namespace std;// 二值化阈值
#define GRAY_THRESH 150// 直线上点的个数
#define HOUGH_VOTE 50int main(int argc, char **argv)
{//Read a single-channel imageconst 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_<float>(padded), Mat::zeros(padded.size(), CV_32F) };Mat comImg;//Merge into a double-channel imagemerge(planes, 2, comImg);//Use the same image as input and output,//so that the results can fit in Mat welldft(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 evenmagMat = 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 cornersint 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 imagethreshold(magImg, magImg, GRAY_THRESH, 255, CV_THRESH_BINARY);imshow("mag_binary", magImg);//imwrite("imageText_bin.jpg",magImg);//Find lines with Hough Transformationvector<Vec2f> 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<numLines; l++){float rho = lines[l][0], theta = lines[l][1];Point pt1, pt2;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));line(linImg, pt1, pt2, Scalar(255, 0, 0), 3, 8, 0);}imshow("lines", linImg);//imwrite("imageText_line.jpg",linImg);if (lines.size() == 3){cout << "found three angels:" << endl;cout << lines[0][1] * 180 / CV_PI << endl << lines[1][1] * 180 / CV_PI << endl << lines[2][1] * 180 / CV_PI << endl << endl;}//Find the proper angel from the three found angelsfloat angel = 0;float piThresh = (float)CV_PI / 90;float pi2 = CV_PI / 2;for (int l = 0; l<numLines; l++){float theta = lines[l][1];if (abs(theta) < piThresh || abs(theta - pi2) < piThresh)continue;else{angel = theta;break;}}//Calculate the rotation angel//The image has to be square,//so that the rotation angel can be calculate rightangel = angel<pi2 ? angel : angel - CV_PI;if (angel != pi2){float angelT = srcImg.rows*tan(angel) / srcImg.cols;angel = atan(angelT);}float angelD = angel * 180 / (float)CV_PI;cout << "the rotation angel to be applied:" << endl << angelD << endl << endl;//Rotate the image to recoverMat rotMat = getRotationMatrix2D(center, angelD, 1.0);Mat dstImg = Mat::ones(srcImg.size(), CV_8UC3);warpAffine(srcImg, dstImg, rotMat, srcImg.size(), 1, 0, Scalar(255, 255, 255));imshow("result", dstImg);//imwrite("imageText_D.jpg",dstImg);waitKey(0);return 0;
}
opencv4x
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgcodecs/legacy/constants_c.h>
#include <iostream>using namespace cv;
using namespace std;// 二值化阈值
#define GRAY_THRESH 150// 直线上点的个数
#define HOUGH_VOTE 50int main(int argc, char **argv)
{//Read a single-channel imageconst char* filename = argv[1];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_<float>(padded), Mat::zeros(padded.size(), CV_32F) };Mat comImg;//Merge into a double-channel imagemerge(planes, 2, comImg);//Use the same image as input and output,//so that the results can fit in Mat welldft(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 evenmagMat = 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 cornersint 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);normalize(magMat, magMat, 0, 1, NORM_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 imagethreshold(magImg, magImg, GRAY_THRESH, 255, cv::THRESH_BINARY);imshow("mag_binary", magImg);//imwrite("imageText_bin.jpg",magImg);//Find lines with Hough Transformationvector<Vec2f> 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<numLines; l++){float rho = lines[l][0], theta = lines[l][1];Point pt1, pt2;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));line(linImg, pt1, pt2, Scalar(255, 0, 0), 3, 8, 0);}imshow("lines", linImg);//imwrite("imageText_line.jpg",linImg);if (lines.size() == 3){cout << "found three angels:" << endl;cout << lines[0][1] * 180 / CV_PI << endl << lines[1][1] * 180 / CV_PI << endl << lines[2][1] * 180 / CV_PI << endl << endl;}//Find the proper angel from the three found angelsfloat angel = 0;float piThresh = (float)CV_PI / 90;float pi2 = CV_PI / 2;for (int l = 0; l<numLines; l++){float theta = lines[l][1];if (abs(theta) < piThresh || abs(theta - pi2) < piThresh)continue;else{angel = theta;break;}}//Calculate the rotation angel//The image has to be square,//so that the rotation angel can be calculate rightangel = angel<pi2 ? angel : angel - CV_PI;if (angel != pi2){float angelT = srcImg.rows*tan(angel) / srcImg.cols;angel = atan(angelT);}//float angelD = angel * 180 / (float)CV_PI;float angelD = angel * 180 / (float)CV_PI;cout << "the rotation angel to be applied: "<< angelD << endl << endl;//Rotate the image to recoverMat rotMat = getRotationMatrix2D(center, angelD, 1.0);Mat dstImg = Mat::ones(srcImg.size(), CV_8UC3);warpAffine(srcImg, dstImg, rotMat, srcImg.size(), 1, 0, Scalar(255, 255, 255));imshow("result", dstImg);imwrite("imageText_D.jpg",dstImg);waitKey(0);return 0;
}
CMakeLists.txt
project( main )
cmake_minimum_required(VERSION 3.10)
#添加头文件路径
include_directories(/usr/local/include /usr/local/include/opencv4 /usr/local/include/opencv4/opencv2)
#添加库文件路径
link_directories(/usr/local/lib)add_executable(main test.cpp)
target_link_libraries( main -lopencv_core -lopencv_highgui -lopencv_imgproc -lopencv_imgcodecs)
3.四点透视 + 角度 + 旋转
#include "opencv2/imgproc.hpp"
#include "opencv2/highgui.hpp"
#include <iostream>
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<Vec2f> 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;
}
opencv4.x
#include "opencv2/imgproc.hpp"
#include "opencv2/highgui.hpp"
#include <iostream>
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, COLOR_GRAY2BGR);//通过霍夫变换检测直线vector<Vec2f> 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, 1000)); //根据先验知识,估计好文本的长宽,再裁剪下来imshow("裁剪之后", resulyImage);imwrite("recified.jpg", resulyImage);
}int main()
{ImageRecify("test.jpg", "FinalImage.jpg");waitKey();return 0;
}
4.检测矩形轮廓 + 角度 + 旋转
#include "opencv2/imgproc.hpp"
#include "opencv2/highgui.hpp"
#include <iostream>
using namespace cv;
using namespace std;
#include <algorithm>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<Point> contours;vector<vector<Point> > 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);elseangle = -angle;cout << "after angle : " << angle << endl;//新建一个感兴趣的区域图,大小跟原图一样大 Mat RoiSrcImg(srcImg.rows, srcImg.cols, CV_8UC3); //注意这里必须选CV_8UC3RoiSrcImg.setTo(0); //颜色都设置为黑色 //imshow("新建的ROI", RoiSrcImg);//对得到的轮廓填充一下 drawContours(binImg, f_contours, 0, Scalar(255), CV_FILLED);//抠图到RoiSrcImgsrcImg.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();
}
opencv4.x
#include "opencv2/imgproc.hpp"
#include "opencv2/highgui.hpp"
#include <opencv2/imgproc/types_c.h>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <iostream>using namespace cv;
using namespace std;
#include <algorithm>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<Point> contours;vector<vector<Point> > 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];RotatedRect rect = minAreaRect(Mat(contours));float angle = rect.angle;cout << "before angle : " << angle << endl;if (angle < -45)angle = (90 + angle);elseangle = -angle;cout << "after angle : " << angle << endl;//新建一个感兴趣的区域图,大小跟原图一样大 Mat RoiSrcImg(srcImg.rows, srcImg.cols, CV_8UC3); //注意这里必须选CV_8UC3RoiSrcImg.setTo(0); //颜色都设置为黑色 //imshow("新建的ROI", RoiSrcImg);//对得到的轮廓填充一下 drawContours(binImg, f_contours, 0, Scalar(255), cv::FILLED);//抠图到RoiSrcImgsrcImg.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);imwrite("recified.jpg", RatationedImg);
}int main()
{GetContoursPic("test.jpg", "FinalImage.jpg");waitKey();return 0;
}
参考
- 榴莲小怪兽 opencv-图片矫正
- OpenCV利用透视变换矫正图像