环境:OpenCV4.5.1 + VS2019
目录
1. 准备工作:
1.1 “Resources"文件
“Resources"文件下载链接:
https://pan.baidu.com/s/1uzVDwl8lD2qVTY1bFlhF1A
提取码:5n48
2. 主要代码
2.1 图像预处理
Mat preProcessing(Mat imgOriginal) {
cvtColor(imgOriginal, imgGray, COLOR_BGR2GRAY); //灰度
GaussianBlur(imgGray, imgBlur, Size(3, 3), 3, 0); //高斯模糊(参数四和五:sigmaX 和 sigmaY
Canny(imgBlur, imgCanny, 25, 75); //边缘检测(参数四和五:两个阈值
Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3)); //返回指定形状和尺寸的结构元素(参数一:指定形状(这里为矩形);参数二指定尺寸
dilate(imgCanny, imgDil, kernel); //膨胀(参数三:膨胀内核
return imgDil;
}
2.2 找出最大矩形的轮廓(文件轮廓) 的四点位置
vector<Point> getContours(Mat imgDil) {
vector<vector<Point>> contours; //轮廓组
vector<Vec4i> hierarchy; // 向量内每个元素包含了4个int型的 向量
findContours(imgDil, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE); //检测轮廓
// 参数二:输入的轮廓组 参数三:画第几条轮廓(-1为负数表示全画) 参数五:线宽
// 参数四:检测轮廓的方法(这里是只检测外轮廓)) 参数五:表示一条轮廓的方法(这里是只存储水平,垂直,对角直线的起始点)
//drawContours(img, contours, -1, Scalar(255, 0, 255), 2); //画出轮廓
vector<vector<Point>> conPoly(contours.size());
vector<Point> biggest;
int maxArea = 0;
for (int i = 0; i < contours.size(); i++)
{
int area = contourArea(contours[i]); //轮廓面积
//cout << area << endl;
// 通过面积大小过滤(去噪)
if (area > 1000)
{
float peri = arcLength(contours[i], true); //弧长(轮廓周长) (参数一:图像轮廓;参数二:是否闭合)
approxPolyDP(contours[i], conPoly[i], 0.02 * peri, true); //对图像轮廓点 进行 多边形拟合
//参数一/二:输入/输出的点集 参数三:指定精度 参数四:是否闭合
if (area > maxArea && conPoly[i].size() == 4) {
maxArea = area;
biggest = { conPoly[i][0], conPoly[i][1], conPoly[i][2], conPoly[i][3] };
//drawContours(imgOriginal, conPoly, i, Scalar(255, 0, 255), 2); //画出轮廓
// 参数二:输入的轮廓组 参数三:画第i条轮廓(负数表示全画) 参数五:线宽
}
}
}
return biggest;
}
2.3 可视化文件四点
void drawPoints(vector<Point> points, Scalar color) {
for (int i = 0; i < points.size(); i++) {
circle(imgOriginal, points[i], 5, color, FILLED);
putText(imgOriginal, to_string(i), { points[i].x - 5,points[i].y - 5 }, FONT_HERSHEY_PLAIN, 3, color, 3);
}
}
2.4 重新将矩形四点按“Z”排序
vector<Point> reorder(vector<Point> points) {
vector<Point> newPoints;
vector<int> sumPoints, subPoints;
for (int i = 0; i < points.size(); i++) {
sumPoints.push_back(points[i].x + points[i].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
// (x+y)min为左上;(x+y)max为右下;(x-y)max为右上;(x-y)min为左下
return newPoints;
}
2.5 Wrap 文件矫正
Mat getWrap(Mat img, vector<Point> points, float w, float h) {
Mat imgWarp;
Point2f src[4] = { points[0],points[1],points[2],points[3] }; //扭曲前的四个点坐标(顺序为Z)
Point2f dst[4] = { {0.0f,0.0f},{w,0.0f},{0.0f,h},{w,h} }; // 后
Mat matrix = getPerspectiveTransform(src, dst); //获取 透视变换矩阵
warpPerspective(img, imgWarp, matrix, Point(w, h)); //透视变换(参数三:透视变换矩阵M;参数四:输出矩阵的尺寸)
return imgWarp;
}
2.6 适当修剪边缘
// 适当修剪边缘
int cropValue = 5;
Rect roi(cropValue, cropValue, w - (2 * cropValue), h - (2 * cropValue));
imgCrop = imgWarp(roi);
2.7 实现
#include <opencv2/imgcodecs.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
#include <iostream>
//项目二:文件扫描器
using namespace cv;
using namespace std;
Virtual Painter //
Mat imgOriginal, imgGray, imgBlur, imgCanny, imgDil;
Mat imgThre, imgWarp, imgCrop;
vector<Point> initialPoints, docPoints;
float w = 420, h = 596; // A4的尺寸*2
Mat preProcessing(Mat imgOriginal) {
cvtColor(imgOriginal, imgGray, COLOR_BGR2GRAY); //灰度
GaussianBlur(imgGray, imgBlur, Size(3, 3), 3, 0); //高斯模糊(参数四和五:sigmaX 和 sigmaY
Canny(imgBlur, imgCanny, 25, 75); //边缘检测(参数四和五:两个阈值
Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3)); //返回指定形状和尺寸的结构元素(参数一:指定形状(这里为矩形);参数二指定尺寸
dilate(imgCanny, imgDil, kernel); //膨胀(参数三:膨胀内核
return imgDil;
}
vector<Point> getContours(Mat imgDil) {
vector<vector<Point>> contours; //轮廓组
vector<Vec4i> hierarchy; // 向量内每个元素包含了4个int型的 向量
findContours(imgDil, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE); //检测轮廓
// 参数二:输入的轮廓组 参数三:画第几条轮廓(-1为负数表示全画) 参数五:线宽
// 参数四:检测轮廓的方法(这里是只检测外轮廓)) 参数五:表示一条轮廓的方法(这里是只存储水平,垂直,对角直线的起始点)
//drawContours(img, contours, -1, Scalar(255, 0, 255), 2); //画出轮廓
vector<vector<Point>> conPoly(contours.size());
vector<Point> biggest;
int maxArea = 0;
for (int i = 0; i < contours.size(); i++)
{
int area = contourArea(contours[i]); //轮廓面积
//cout << area << endl;
// 通过面积大小过滤(去噪)
if (area > 1000)
{
float peri = arcLength(contours[i], true); //弧长(轮廓周长) (参数一:图像轮廓;参数二:是否闭合)
approxPolyDP(contours[i], conPoly[i], 0.02 * peri, true); //对图像轮廓点 进行 多边形拟合
//参数一/二:输入/输出的点集 参数三:指定精度 参数四:是否闭合
if (area > maxArea && conPoly[i].size() == 4) {
maxArea = area;
biggest = { conPoly[i][0], conPoly[i][1], conPoly[i][2], conPoly[i][3] };
//drawContours(imgOriginal, conPoly, i, Scalar(255, 0, 255), 2); //画出轮廓
// 参数二:输入的轮廓组 参数三:画第i条轮廓(负数表示全画) 参数五:线宽
}
}
}
return biggest;
}
void drawPoints(vector<Point> points, Scalar color) {
for (int i = 0; i < points.size(); i++) {
circle(imgOriginal, points[i], 5, color, FILLED);
putText(imgOriginal, to_string(i), { points[i].x - 5,points[i].y - 5 }, FONT_HERSHEY_PLAIN, 3, color, 3);
}
}
vector<Point> reorder(vector<Point> points) {
vector<Point> newPoints;
vector<int> sumPoints, subPoints;
for (int i = 0; i < points.size(); i++) {
sumPoints.push_back(points[i].x + points[i].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
// (x+y)min为左上;(x+y)max为右下;(x-y)max为右上;(x-y)min为左下
return newPoints;
}
Mat getWrap(Mat img, vector<Point> points, float w, float h) {
Mat imgWarp;
Point2f src[4] = { points[0],points[1],points[2],points[3] }; //扭曲前的四个点坐标(顺序为Z)
Point2f dst[4] = { {0.0f,0.0f},{w,0.0f},{0.0f,h},{w,h} }; // 后
Mat matrix = getPerspectiveTransform(src, dst); //获取 透视变换矩阵
warpPerspective(img, imgWarp, matrix, Point(w, h)); //透视变换(参数三:透视变换矩阵M;参数四:输出矩阵的尺寸)
return imgWarp;
}
// Image //
void main() {
//string path = "C:\\Users\\ERDONGC\\Desktop\\CSDN\\微信图片_20220215203616.jpg";
string path = "Resources/paper.jpg";
imgOriginal = imread(path);
//resize(imgOriginal, imgOriginal, Size(), 0.5, 0.5);
// Preprocessing 图像预处理
imgThre = preProcessing(imgOriginal);
// Get Contours - Biggest 找出最大矩形的轮廓(文件轮廓) 的四点位置
initialPoints = getContours(imgThre);
//drawPoints(initialPoints, Scalar(0, 0, 255)); //可视化文件四点
// 重新将矩形四点按Z排序
docPoints = reorder(initialPoints);
//drawPoints(docPoints, Scalar(0, 255, 0)); //可视化重新排序后的文件四点
// Wrap 文件矫正
imgWarp = getWrap(imgOriginal, docPoints, w, h);
// 适当修剪边缘
int cropValue = 5;
Rect roi(cropValue, cropValue, w - (2 * cropValue), h - (2 * cropValue));
imgCrop = imgWarp(roi);
imshow("Image", imgOriginal);
//imshow("Image Dilation", imgThre);
//imshow("Image Warp", imgWarp);
imshow("Image Crop", imgCrop);
waitKey(0); //写0为无穷大
}
3. 总结
实现功能:
可自动检测图片中的文件部分,并将检测到的文件部分进行矫正和适当修剪后单独显示出来。
未完善地方:
暂只能对图片进行扫描,无法通过摄像头进行实时扫描。


被折叠的 条评论
为什么被折叠?



