[opencv][cpp] 学习手册3:多边形拟合
1. 多边形拟合示例:approxPolyDP
原理
图像截取自课件视频
目的:需要拟合红色的曲线
- 连接曲线首尾两端(S0,E)
- 对曲线上的每一个点做直线的垂线(取一个方向,比如从起始 S0 点到终点 E)
- 每一个垂线都有长度L,当某一个曲线上的点对应的 L 大于阈值 T,那么就记录下这个点 {Sn}
- 以这个点 Sn 为新的起点,连接 E,重复 1
- 得到了 {Sn} = {S1, S2, … ,Sn} 以及 E,这些点既是曲线的拟合点。
- T 越小,拟合越精准。
代码
/**
Created by jacob on 12/31/20.
*/
#include <iostream>
#include <opencv2/opencv.hpp>
#include <opencv2/core/utils/logger.hpp>
using namespace std;
using namespace cv;
namespace cvlog = cv::utils::logging;
int main() {
cvlog::setLogLevel(cvlog::LOG_LEVEL_INFO);
string filename = "../img/book.jpg";
Mat src = imread(filename, IMREAD_COLOR);
resize(src, src, Size(src.cols / 2, src.rows / 2));
// 1.将彩色图像转成灰度图像
Mat grayImg;
cvtColor(src, grayImg, COLOR_BGR2GRAY);
imshow("grayImg", grayImg);
// 2. 转成二值图(黑底白字,白底黑字有不同的处理方式)
Mat binary;
threshold(grayImg, binary, 0, 255, THRESH_BINARY | THRESH_TRIANGLE);
imshow("binary1", binary);
// 膨胀操作(可选)
Mat kernel = getStructuringElement(MORPH_RECT, Size(5, 5));
dilate(binary, binary, kernel, Point(-1, -1), 4);
erode(binary, binary, kernel, Point(-1, -1), 4);
bitwise_not(binary, binary);
imshow("binary dilate", binary);
// 定义一个画布,用于保存最大轮廓图像
Mat maxImg = cv::Mat::zeros(src.size(), CV_8UC3);
// 3. 找出工牌的轮廓
vector<vector<Point>> contours;
vector<cv::Vec4i> hierarchy;
findContours(binary, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
CV_LOG_INFO(NULL, "contours size: " << contours.size())
// 定义一个变量,存储所有多边形轮廓
vector<vector<Point>> polyContours;
for (int i = 0; i < contours.size(); ++i) {
// 当前的轮廓所有的点
vector<Point> contour = contours[i];
// CV_LOG_INFO(NULL, "contour: [" << i << "]\n" << contour)
// 计算面积
double area = contourArea(contour);
if (area > 10000) {
// 在一个空白的图像中画出轮廓
drawContours(maxImg, contours, i, Scalar(0, 0, 255), 3);
// 根据轮廓,拟合出多边形出来
vector<Point> polyPoint;
approxPolyDP(contour, polyPoint, 100, true);
CV_LOG_INFO(NULL, "拟合结果点的数量:" << polyPoint.size())
polyContours.push_back(polyPoint);
drawContours(maxImg, polyContours, -1, Scalar(0, 255, 255), 1);
drawContours(src, polyContours, -1, Scalar(0, 255, 255), 1);
}
}
// 输出四边形拟合点
vector<Point> polyPoint = polyContours[0];
for (int j = 0; j < polyPoint.size(); ++j) { // 左上角: 1, 右上角 0 左下角 2 右下角3
cout << "点:" << polyPoint[j] << endl;
}
// 使用霍夫直线
Mat maxGrayImg;
cvtColor(maxImg, maxGrayImg, COLOR_BGR2GRAY);
// 4. 找到边缘的交点
vector<Point2f> sourcePoints = {polyPoint[1], polyPoint[0], polyPoint[2], polyPoint[3]};
vector<Point2f> targetPoints = {Point2f(0, 0), Point2f(480, 0), Point2f(0, 640), Point2f(480, 640)};
// 5. 运用透视变换
Mat m = getPerspectiveTransform(sourcePoints, targetPoints);
Mat dst;
warpPerspective(src, dst, m, Size(480, 640));
imshow("maxImg", maxImg);
imshow("src", src);
imshow("dst", dst);
waitKey(0);
return 0;
}
运行结果
2. 心得
- 没有标准化的处理方法
- 要对图像进行预处理,resize,dilate,erode,开闭处理等
- 此示例中对于白底黑字,黑底白字的处理方式不同,前者在预处理后应该需要进行一次二值图反转操作 bitwise_not