[OpenCV]自动计算坐标进行透视变换矫正
头文件:DegreeTransform.h
// // Created by leoxae on 19-6-24. // #ifndef OPENCVDEMO_DEGREETRANSFORM_H #define OPENCVDEMO_DEGREETRANSFORM_H #include <opencv2/opencv.hpp> #include <iostream> #include <opencv2/highgui/highgui.hpp> #include <opencv2/imgproc/imgproc.hpp> #include <opencv2/imgproc.hpp> using namespace std; using namespace cv; class DegreeTransform{ public: static bool sort_corners(std::vector<cv::Point2f>& corners) ; static Point2f computeIntersect(cv::Vec4i a, cv::Vec4i b) ; static bool IsBadLine(int a, int b) ; static bool x_sort(const Point2f & m1, const Point2f & m2) ; static void sortCorners(std::vector<cv::Point2f>& corners, cv::Point2f center) ; static void CalcDstSize(const vector<cv::Point2f>& corners) ; public: static int correct(Mat src) ; //string path }; #endif //OPENCVDEMO_DEGREETRANSFORM_H
源码文件:DegreeTransform.cpp
// // Created by leoxae on 19-6-24. // #include "DegreeTransform.h" cv::Point2f center(0, 0); /** * 角点坐标计算及其排序 * @param corners * @return */ bool DegreeTransform::sort_corners(std::vector<cv::Point2f>& corners) { std::vector<cv::Point2f> top, bot; cv::Point2f tmp_pt; std::vector<cv::Point2f> olddata = corners; if (corners.size() != 4) { return false; } for (size_t i = 0; i < corners.size(); i++) { for (size_t j = i + 1; j<corners.size(); j++) { if (corners[i].y < corners[j].y) { tmp_pt = corners[i]; corners[i] = corners[j]; corners[j] = tmp_pt; } } } top.push_back(corners[0]); top.push_back(corners[1]); bot.push_back(corners[2]); bot.push_back(corners[3]); if (top.size() == 2 && bot.size() == 2) { corners.clear(); cv::Point2f tl = top[0].x > top[1].x ? top[1] : top[0]; cv::Point2f tr = top[0].x > top[1].x ? top[0] : top[1]; cv::Point2f bl = bot[0].x > bot[1].x ? bot[1] : bot[0]; cv::Point2f br = bot[0].x > bot[1].x ? bot[0] : bot[1]; corners.push_back(tl); corners.push_back(tr); corners.push_back(br); corners.push_back(bl); return true; } else { corners = olddata; return false; } } /** * 计算相交 * @param a * @param b * @return */ cv::Point2f DegreeTransform::computeIntersect(cv::Vec4i a, cv::Vec4i b) { int x1 = a[0], y1 = a[1], x2 = a[2], y2 = a[3]; int x3 = b[0], y3 = b[1], x4 = b[2], y4 = b[3]; if (float d = ((float)(x1 - x2) * (y3 - y4)) - ((y1 - y2) * (x3 - x4))) { cv::Point2f pt; pt.x = ((x1*y2 - y1*x2) * (x3 - x4) - (x1 - x2) * (x3*y4 - y3*x4)) / d; pt.y = ((x1*y2 - y1*x2) * (y3 - y4) - (y1 - y2) * (x3*y4 - y3*x4)) / d; return pt; } else return cv::Point2f(-1, -1); } /** * 判断检测的线段是否找错 * @param a * @param b * @return */ bool DegreeTransform::IsBadLine(int a, int b) { if (a * a + b * b < 100) { return true; } else { return false; } } //子排序 bool DegreeTransform::x_sort(const Point2f & m1, const Point2f & m2) { return m1.x > m2.x; //return m1.x < m2.x; } /** * 确定四个点的中心线 * @param corners * @param center */ void DegreeTransform::sortCorners(std::vector<cv::Point2f>& corners, cv::Point2f center) { std::vector<cv::Point2f> top, bot; vector<Point2f> backup = corners; //先按x的大小给四个点排序 sort(corners.begin(),corners.end(), x_sort); for (int i = 0; i < corners.size(); i++) { //为了避免三个顶点都在top的情况 if (corners[i].y < center.y && top.size() < 2) top.push_back(corners[i]); else bot.push_back(corners[i]); } corners.clear(); if (top.size() == 2 && bot.size() == 2) { cout << "log" << endl; cv::Point2f tl = top[0].x > top[1].x ? top[1] : top[0]; cv::Point2f tr = top[0].x > top[1].x ? top[0] : top[1]; cv::Point2f bl = bot[0].x > bot[1].x ? bot[1] : bot[0]; cv::Point2f br = bot[0].x > bot[1].x ? bot[0] : bot[1]; corners.push_back(tl); corners.push_back(tr); corners.push_back(br); corners.push_back(bl); } else { corners = backup; } } int g_dst_hight; //最终图像的高度 int g_dst_width; //最终图像的宽度 /** * 计算效果图尺寸大小 * @param corners */ void DegreeTransform::CalcDstSize(const vector<cv::Point2f>& corners) { int h1 = sqrt((corners[0].x - corners[3].x)*(corners[0].x - corners[3].x) + (corners[0].y - corners[3].y)*(corners[0].y - corners[3].y)); int h2 = sqrt((corners[1].x - corners[2].x)*(corners[1].x - corners[2].x) + (corners[1].y - corners[2].y)*(corners[1].y - corners[2].y)); g_dst_hight = MAX(h1, h2); int w1 = sqrt((corners[0].x - corners[1].x)*(corners[0].x - corners[1].x) + (corners[0].y - corners[1].y)*(corners[0].y - corners[1].y)); int w2 = sqrt((corners[2].x - corners[3].x)*(corners[2].x - corners[3].x) + (corners[2].y - corners[3].y)*(corners[2].y - corners[3].y)); g_dst_width = MAX(w1, w2); } int DegreeTransform::correct(Mat src)//string path { // Mat src = imread(path); imshow("src img", src); ///预处理 二值化+高斯滤波+膨胀+canny边缘提取 Mat source = src.clone(); Mat bkup = src.clone(); ///LUT映射表 // Mat gray ; // cvtColor(src, gray, COLOR_RGB2GRAY); //二值化 // imshow("gray", gray); // // uchar lutData[256]; // for (int i = 0; i<256; i++) // { // if(i<=100) // lutData[i] = 0; // if (i > 100 && i <= 200) // lutData[i] = 100; // if (i > 200) // lutData[i] = 255; // } // Mat lut(1, 256, CV_8UC1, lutData); // Mat d_lut; // // namedWindow("anjis", WINDOW_AUTOSIZE); // namedWindow("anjis1", WINDOW_AUTOSIZE); // imshow("anjis",gray); // LUT(gray, lut, d_lut); // imshow("anjis1", d_lut); // // Mat canny ; // Canny(d_lut,canny,30,90,3) ; // imshow("canny",canny) ; Mat gray ; cvtColor(src, gray, COLOR_RGB2GRAY); //二值化 // imshow("gray", gray); Mat equal ; equalizeHist(gray, equal); // imshow("equal", equal); Mat gauss ; GaussianBlur(equal, gauss, Size(5, 5), 0, 0); //高斯滤波 //定义自定义核 Mat element = getStructuringElement(MORPH_RECT, Size(3, 3)); //MORPH_RECT表示矩形卷积核 //膨胀操作 Mat dila ; dilate(gauss, dila, element); // imshow("dilate", img); //边缘提取 Mat canny ; Canny(dila, canny, 30, 120, 3); imshow("get contour", canny); ///轮廓查找并筛选 vector<vector<Point>> contours; vector<vector<Point>> f_contours; std::vector<cv::Point> approx2; //查找轮廓,RETR_EXTERNAL只要检索外框 findContours(canny, f_contours, RETR_EXTERNAL, 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.push_back(f_contours[index]); cout << "轮廓数量:" << contours.size() << endl; //因为是找出最外层轮廓,因此理论上轮廓只有一个 vector<Point> tmp = contours[0]; for (int line_type = 1; line_type <= 3; line_type++) { cout << "line_type: " << line_type << endl; Mat black = canny.clone(); black.setTo(0); drawContours(black, contours, 0, Scalar(255), line_type); imshow("show contour", black); std::vector<Vec4i> lines; std::vector<cv::Point2f> corners; std::vector<cv::Point2f> approx; int para = 10; int flag = 0; int round = 0; for (; para < 300; para++) { cout << "round: " << ++round << endl; lines.clear(); corners.clear(); approx.clear(); center = Point2f(0, 0); cv::HoughLinesP(black, lines, 1, CV_PI / 180, para, 30, 10); //gai //过滤距离太近的直线 std::set<int> ErasePt; for (int i = 0; i < lines.size(); i++) { for (int j = i + 1; j < lines.size(); j++) { if (IsBadLine(abs(lines[i][0] - lines[j][0]), abs(lines[i][1] - lines[j][1])) && (IsBadLine(abs(lines[i][2] - lines[j][2]), abs(lines[i][3] - lines[j][3])))) { ErasePt.insert(j);//将找错的线加入集合 } } } int Num = lines.size(); while (Num != 0) { std::set<int>::iterator j = ErasePt.find(Num); if (j != ErasePt.end()) { lines.erase(lines.begin() + Num - 1); } Num--; } if (lines.size() != 4) { continue; } //计算直线的交点,保存在图像范围内的部分 for (int i = 0; i < lines.size(); i++) { for (int j = i + 1; j < lines.size(); j++) { cv::Point2f pt = computeIntersect(lines[i], lines[j]); //保证交点在图像的范围之内 if (pt.x >= 0 && pt.y >= 0 && pt.x <= src.cols && pt.y <= src.rows) corners.push_back(pt); cout << "pt:" << pt << endl ; } } if (corners.size() != 4) { continue; } #if 1 bool IsGoodPoints = true; //保证点与点的距离足够大以排除错误点 for (int i = 0; i < corners.size(); i++) { for (int j = i + 1; j < corners.size(); j++) { int distance = sqrt((corners[i].x - corners[j].x)*(corners[i].x - corners[j].x) + (corners[i].y - corners[j].y)*(corners[i].y - corners[j].y)); if (distance < 5) { IsGoodPoints = false; } } } if (!IsGoodPoints) continue; #endif cv::approxPolyDP(cv::Mat(corners), approx, cv::arcLength(cv::Mat(corners), true) * 0.02, true); if (lines.size() == 4 && corners.size() == 4 && approx.size() == 4) { flag = 1; break; } } // 取得中心 Get mass center for (int i = 0; i < corners.size(); i++) center += corners[i]; center *= (1. / corners.size()); if (flag) { cout << "we found it!" << endl; cv::circle(bkup, corners[0], 3, CV_RGB(255, 0, 0), -1); cv::circle(bkup, corners[1], 3, CV_RGB(0, 255, 0), -1); cv::circle(bkup, corners[2], 3, CV_RGB(0, 0, 255), -1); cv::circle(bkup, corners[3], 3, CV_RGB(255, 255, 255), -1); cv::circle(bkup, center, 3, CV_RGB(255, 0, 255), -1); imshow("backup", bkup); cout << "corners size" << corners.size() << endl; bool sort_flag = sort_corners(corners); // if (!sort_flag) cout << "fail to sort" << endl; // sortCorners(corners, center); cout << "corners size" << corners.size() << endl; cout << "tl:" << corners[0] << endl; cout << "tr:" << corners[1] << endl; cout << "br:" << corners[2] << endl; cout << "bl:" << corners[3] << endl; //计算效果图尺寸大小 CalcDstSize(corners); cv::Mat quad = cv::Mat::zeros(g_dst_hight, g_dst_width, CV_8UC3); std::vector<cv::Point2f> quad_pts; //目的坐标矩阵的构造 quad_pts.push_back(cv::Point2f(0, 0)); quad_pts.push_back(cv::Point2f(quad.cols, 0)); quad_pts.push_back(cv::Point2f(quad.cols, quad.rows)); quad_pts.push_back(cv::Point2f(0, quad.rows)); cv::Mat transmtx = cv::getPerspectiveTransform(corners, quad_pts); cv::warpPerspective(source, quad, transmtx, quad.size()); imshow("find", bkup); cv::imshow("dst1", quad); cv::waitKey(0); return 0; } } cout << "can not transform!" << endl; waitKey(0); }
主函数:main()
#include "DegreeTransform.h" using namespace std; using namespace cv; #define CLOCKS_PER_SEC ((clock_t)1000) int main() { //计算时间 clock_t start_time, end_time; start_time = clock(); string path = "/home/leoxae/KeekoRobot/TestPic/编程贴纸/bc12.jpg" ;//home/leoxae/KeekoRobot/TestPic/20190614/大班/内外倾斜/2.jpg //home/leoxae/KeekoRobot/TestPic/编程贴纸/bc24.jpg Mat src = imread(path) ; DegreeTransform::correct(src) ; end_time = clock(); cout << "运行时间==" << ((end_time - start_time) / CLOCKS_PER_SEC) << "ms" << endl; return 0;
运行效果图:
(原图)
(canny检测)
(显示最大矩形轮廓)
(在ROI上找到角点坐标并标记)
(效果图)