这篇文章主要介绍了怎么用C++ OpenCV实现文档矫正功能的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇怎么用C++ OpenCV实现文档矫正功能文章都会有所收获,下面我们一起来看看吧。
需求
将一个斜着拍摄的文档矫正成正的,如图所示:
思路
读取原始图像,若图像太大可以先进行缩放处理,并获取原始图像的宽和高
对图像进行预处理得到边缘,依次进行灰度处理、高斯模糊、边缘检测、膨胀、腐蚀。
找到最大的轮廓,并提取角点
进行降噪处理:检测轮廓面积,只保留大于阈值面积的轮廓
计算每个轮廓的周长,使用DP算法计算出轮廓点的个数,规则为周长*0.02
找到图像中面积最大的,且角点为4的轮廓
将找到的四个角点排列成一个固定的顺序,排列后的顺序为:左上角-右上角-左下角-右下角
将每个点的xy坐标值相加(x+y),左上角的点的坐标和应该是最小的,右下角的点的坐标和应该是最大的
将每个点的xy坐标值相减(x-y),左下角的点的坐标差应该是最小的,右上角的点的坐标差应该是最大的
重新排列四个角点
进行透视变换
根据变换前及变换后的四个角点,创建变换矩阵
根据变换矩阵对图像进行透视变换
若透视变换后有一些毛边,按需要进行裁剪,裁剪后重新调整比例
创建一个矩形用来裁剪,并设定四周裁剪5像素
裁剪后重新调整图像宽高
显示变换后图像
代码
代码中均有详细注释,请仔细阅读
#include <iostream>#include<opencv2/opencv.hpp>#include <opencv2/highgui.hpp>#include <opencv2/imgproc.hpp>using namespace cv;using namespace std;// 一些定义Mat image_origin, // 原始图像image_gray, // 灰度处理后的图像image_blur, // 高斯模糊处理后的图像image_canny, // 边缘检测后的图像image_dilate, // 膨胀后的图像image_erode, // 腐蚀后的图像image_preprocess, // 预处理后的图像image_trans, // 透视变换后的图像image_crop; // 裁剪后的图像vector<Point> origin_points, // 重新排列前的角点 reorder_points; // 重新排列后的角点 int origin_width = 0, origin_height = 0;Mat PreProcess(const Mat& image, int display){// 灰度处理cvtColor(image, image_gray, COLOR_BGR2GRAY);// 高斯模糊GaussianBlur(image_gray, image_blur, Size(3, 3), 3, 0);// 边缘检测(边缘检测前对图像进行一次高斯模糊)Canny(image_blur, image_canny, 50, 150);// 膨胀和腐蚀(有时进行边缘检测的时候,没有被完全填充,或者无法正确检测,可以用膨胀和腐蚀)// 创建一个用于膨胀和腐蚀的内核,后面的数字越大膨胀的越多(数字要用奇数)Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3));// 膨胀dilate(image_canny, image_dilate, kernel);// 腐蚀//erode(image_dilate, image_erode, kernel);// 显示预处理效果if(display == 1){imshow("灰度处理后的图像", image_gray);imshow("高斯模糊后的图像", image_blur);imshow("边缘检测后的图像", image_canny);imshow("膨胀后的图像", image_dilate);//imshow("腐蚀后的图像", image_erode);}else if(display == 2){imshow("预处理后的图像", image_dilate);}return image_dilate;}vector<Point> GetMaxContour(const Mat& img_input){vector<vector<Point>> contours;vector<Vec4i> hierarchy;findContours(img_input, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);//// 不全输出,在下文只输出角点//drawContours(image, contours, -1, Scalar(255, 0, 255), 2);// 定义轮廓,大小与contours相同,但内层向量中只有角点(例如三角形就是3,四边形就是4,圆形可能七八个)vector<vector<Point>> corners_contours(contours.size());// 定义边界框,大小与contours相同vector<Rect> bounding_box(contours.size());vector<Point> biggest_contours;double max_area = 0;for (int i = 0; i < contours.size(); i++){// 检测轮廓面积double contour_area = contourArea(contours[i]);//cout << area << endl;// 假设图像中有噪声,需要将其过滤,只保留面积大于1000的轮廓if (contour_area > 1000){// 计算每个轮廓的周长double contour_perimeter = arcLength(contours[i], true);// 使用DP算法计算出轮廓点的个数,规则为周长*0.02approxPolyDP(contours[i], corners_contours[i], 0.02 * contour_perimeter, true);// 找到图像中面积最大的,且角点为4的轮廓if (contour_area > max_area && corners_contours[i].size() == 4 ) {//drawContours(image_origin, conPoly, i, Scalar(255, 0, 255), 5);biggest_contours = { corners_contours[i][0],corners_contours[i][1] ,corners_contours[i][2] ,corners_contours[i][3] };max_area = contour_area;}//// 只绘制角点之间的边框线,Debug用,取消注释可以看到检测出的所有边界框//drawContours(image_origin, corners_contours, i, Scalar(255, 0, 255), 2);//rectangle(image_origin, bounding_box[i].tl(), bounding_box[i].br(), Scalar(0, 255, 0), 5);}}// 返回最大的轮廓return biggest_contours;}void DrawPoints(vector<Point> points, const Scalar& color){for (int i = 0; i < points.size(); i++){circle(image_origin, points[i], 10, color, FILLED);putText(image_origin, to_string(i), points[i], FONT_HERSHEY_PLAIN, 4, color, 4);}}vector<Point> ReorderPoints(vector<Point> points){vector<Point> newPoints;vector<int> sumPoints, subPoints;// OpenCV中左上顶点为(0,0),右为x轴正向,下为y轴正向。for (int i = 0; i < 4; i++){// 将每个点的xy坐标值相加(x+y),左上角的点的坐标和应该是最小的,右下角的点的坐标和应该是最大的sumPoints.push_back(points[i].x + points[i].y);// 将每个点的xy坐标值相减(x-y),左下角的点的坐标差应该是最小的,右上角的点的坐标差应该是最大的subPoints.push_back(points[i].x - points[i].y);}// 重新排列newPoints.push_back(points[min_element(sumPoints.begin(), sumPoints.end()) - sumPoints.begin()]); // 0 和的最小值newPoints.push_back(points[max_element(subPoints.begin(), subPoints.end()) - subPoints.begin()]); // 1 差的最大值newPoints.push_back(points[min_element(subPoints.begin(), subPoints.end()) - subPoints.begin()]); // 2 差的最小值newPoints.push_back(points[max_element(sumPoints.begin(), sumPoints.end()) - sumPoints.begin()]); // 3 和的最大值return newPoints;}Mat PerspectiveTrans(const Mat& img, vector<Point> points, float width, float height ){// 前面经过重新排列,四个角点的顺序为:左上角-右上角-左下角-右下角Point2f src[4] = { points[0],points[1],points[2],points[3] };// 变换后的四个角点Point2f dst[4] = { {0.0f,0.0f},{width,0.0f},{0.0f,height},{width,height} };// 创建变换矩阵Mat matrix = getPerspectiveTransform(src, dst);// 透视变换warpPerspective(img, image_trans, matrix, Point(width, height));return image_trans;}int main(){// 1.读取原始图像string path = "res/image_origin.jpg";image_origin = imread(path);//// 若图像太大可以先进行缩放处理//resize(image_origin, image_origin, Size(), 0.5, 0.5);// 获取原始图像的宽和高origin_width = image_origin.size().width;origin_height = image_origin.size().height;// 2.对图像进行预处理得到边缘,依次进行灰度处理、高斯模糊、边缘检测、膨胀、腐蚀。image_preprocess = PreProcess(image_origin, 0);// 3.找到最大的轮廓,并提取角点origin_points = GetMaxContour(image_preprocess);//DrawPoints(origin_points, Scalar(0, 0, 255)); // 红色// 此时发现,角点的顺序不固定,为了后面进行透视变换时与代码中变换后点集的顺序相同,需要将其排列成一个固定的顺序,排列后的顺序为:左上角-右上角-左下角-右下角reorder_points = ReorderPoints(origin_points);//DrawPoints(reorder_points, Scalar(0, 255, 0)); //绿色// 4.透视变换image_trans = PerspectiveTrans(image_origin, reorder_points, origin_width, origin_height);// 透视变换后有一些毛边,若需要可以进行裁剪// 四周裁剪5像素int cropVal= 5;// 创建一个矩形用来裁剪Rect roi(cropVal, cropVal, origin_width - (2 * cropVal), origin_height - (2 * cropVal));image_crop = image_trans(roi);// 裁剪后重新调整比例resize(image_crop, image_crop, Size(origin_width, origin_height));// 5.显示并输出变换后图像imshow("源图像", image_origin);imshow("最终图像", image_crop); imwrite("res/image_output.jpg", image_crop);waitKey(0);}
效果
关于“怎么用C++ OpenCV实现文档矫正功能”这篇文章的内容就介绍到这里,感谢各位的阅读!相信大家对“怎么用C++ OpenCV实现文档矫正功能”知识都有一定的了解,大家如果还想学习更多知识,欢迎关注编程网行业资讯频道。