参考了https://blog.csdn.net/MengchiCMC/article/details/77981112,进行测试。
主要代码如下:
image_rotate_calib.hpp
#ifndef image_rotate_calib_hpp
#define image_rotate_calib_hpp
#include <iostream>
#include <sstream>
#include <string>
#include <ctime>
#include <cstdio>
#include <list>
#include <opencv2/core.hpp>
#include <opencv2/core/utility.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/calib3d.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/videoio.hpp>
#include <opencv2/highgui.hpp>
void image_rotate_line();
#endif /* image_rotate_calib_hpp */
image_rotate_calib.cpp
#include "image_rotate_calib.hpp"
using namespace std;
using namespace cv;
/**
* @brief calculate the angle of two lines by using vector angle formula: cos(thea) = (a*b) / (|a||b|)
* @param line1
* @param line2
* @return result ranges from 0 to pi
*/
double angle_of_lines(const Vec4i& line1, const Vec4i& line2)
{
double moduleLine1 = sqrt(pow(line1[0] - line1[2], 2) + pow(line1[1] - line1[3], 2));
double moduleLine2 = sqrt(pow(line2[0] - line2[2], 2) + pow(line2[1] - line2[3], 2));
double dotProduct = (line1[0] - line1[2]) * (line2[0] - line2[2]) + (line1[1] - line1[3]) * (line2[1] - line2[3]);
return acos(dotProduct / moduleLine1 / moduleLine2) * 180 / CV_PI;
}
/**
* @brief comparison function for sort, sort vector<Vec4i> from small to large accodoring to x of the midpoint of each element
* @param line1
* @param line2
* @return
*/
bool getMinMidX(const cv::Vec4i& line1, const cv::Vec4i& line2)
{
return (line1[0] + line1[2]) < (line2[0] + line2[2]); // Although middle point compared, there is no need to divide 2
}
/**
* @brief comparison function for sort, sort vector<Vec4i> from large to small accodoring to x of the midpoint of each element
* @param line1
* @param line2
* @return
*/
bool getMaxMidX(const cv::Vec4i& line1, const cv::Vec4i& line2)
{
return (line1[0] + line1[2]) > (line2[0] + line2[2]);
}
/**
* @brief comparison function for sort, sort vector<Vec4i> from small to large accodoring to y of the midpoint of each element
* @param line1
* @param line2
* @return
*/
bool getMinMidY(const cv::Vec4i& line1, const cv::Vec4i& line2)
{
return (line1[1] + line1[3]) < (line2[1] + line2[3]);
}
/**
* @brief comparison function for sort, sort vector<Vec4i> from large to small accodoring to y of the midpoint of each element
* @param line1
* @param line2
* @return
*/
bool getMaxMidY(const cv::Vec4i& line1, const cv::Vec4i& line2)
{
return (line1[1] + line1[3]) > (line2[1] + line2[3]);
}
/**
* @brief rotation angle in degrees for correcting tilt
* @param line: for cv::Vec4i& line, [0] is always smaller than [2]
* @return The symbol of the result represnts the direction of rotation to correct tilt.
* Positive values mean counter-clockwise rotation (the coordinate origin is assumed to be the top-left corner).
*/
double angleForCorrect(const cv::Vec4i& line)
{
Vec4i unitXVector(0, 0, 1, 0);
double angle = angle_of_lines(unitXVector, line); // here angle belongs to [0, pi/2]
// @attention: the increment direction of X and Y axis of OpenCV is different from usual rectangular coordinate system. The origin point is in the upper left corner of the image
if (angle < 45)
{
// consider in the horizontal direction
if (line[1] > line[3])
{
angle = -angle;
}
}
else
{
// consider in the vertical direction
if (line[1] > line[3])
{
angle = 90 - angle;
}
else
{
angle = angle - 90;
}
}
return angle;
}
/**
* @brief rotate iamge according to angle
* @param src
* @param dst
* @param angle: rotation angle in degrees. Positive values mean counter-clockwise rotation (the
coordinate origin is assumed to be the top-left corner).
*/
void rotateIamge(cv::Mat& src, cv::Mat& dst, double angle)
{
cv::Point2f center(src.cols / 2, src.rows / 2);
cv::Mat rot = getRotationMatrix2D(center, angle, 1);
cv::Rect box = RotatedRect(center, src.size(), angle).boundingRect(); // get circumscribed rectangle
cv::warpAffine(src, dst, rot, box.size());
}
void image_rotate_line()
{
Mat imgOri = imread("/Users/TEST/test.jpeg");
Mat imgScale;
float scaleFactor = 400.0/imgOri.cols;
resize(imgOri, imgScale, Size(imgOri.cols * scaleFactor, imgOri.rows * scaleFactor));
Mat imgGray;
cvtColor(imgScale, imgGray, COLOR_BGR2GRAY);
Mat imgCanny;
Canny(imgScale, imgCanny, 100, 200);
imshow("canny", imgCanny);
vector<Vec4i> lineAll;
HoughLinesP(imgCanny, lineAll, 1, CV_PI/180, 30, 50, 4);
Mat imgAllLines;
imgScale.copyTo(imgAllLines);
for (int i = 0, steps = lineAll.size(); i < steps; i++)
{
line(imgAllLines, Point(lineAll[i][0], lineAll[i][1]), Point(lineAll[i][2], lineAll[i][3]), Scalar(255, 255, 255), 3, 8);
}
imshow("All lines detected", imgAllLines);
list<Vec4i> linesList;
for(vector<Vec4i>::iterator itor = lineAll.begin(); itor != lineAll.end(); itor++)
{
linesList.push_back(*itor);
}
vector<Vec4i> lineFiltered;
for(list<Vec4i>::iterator itorOuter = linesList.begin(); itorOuter != linesList.end();)
{
for(list<Vec4i>::iterator itorInner = linesList.begin(); itorInner != linesList.end(); itorInner++)
{
// 选出相互垂直的线
if(abs(angle_of_lines(*itorOuter, *itorInner) - 90) < 1)
{
// record these two lines
lineFiltered.push_back(*itorOuter);
lineFiltered.push_back(*itorInner);
itorInner = linesList.erase(itorInner);
itorOuter = linesList.erase(itorOuter);
break;
}
if (itorInner == --linesList.end())
{
if (linesList.size() > 2)
{
itorOuter = linesList.erase(itorOuter); // erase current element when there is no other line perpendicular to it.
}
else
{
itorOuter = linesList.end();
break;
}
}
}
}
Mat imgLinesFiltered;
imgScale.copyTo(imgLinesFiltered);
// draw lines after filtering
for (int i = 0, steps = lineFiltered.size(); i < steps; i++)
{
line(imgLinesFiltered, Point(lineFiltered[i][0], lineFiltered[i][1]), Point(lineFiltered[i][2], lineFiltered[i][3]), Scalar(255, 0, 0), 3, 8);
}
imshow("Lines after filtering", imgLinesFiltered);
double correctAngle = 0.0; // average tilt angle of PCB
if (lineFiltered.size() > 0)
{
// find edge lines of PCB
std::vector<Vec4i> lineEdge;
sort(lineFiltered.begin(), lineFiltered.end(), getMinMidX); // get the line at the far left of the image
lineEdge.push_back(lineFiltered[0]);
sort(lineFiltered.begin(), lineFiltered.end(), getMaxMidX); // get the line at the far right of the image
lineEdge.push_back(lineFiltered[0]);
sort(lineFiltered.begin(), lineFiltered.end(), getMinMidY); // get the line at the top of the image
lineEdge.push_back(lineFiltered[0]);
sort(lineFiltered.begin(), lineFiltered.end(), getMaxMidY); // get the line at the buttom of the image
lineEdge.push_back(lineFiltered[0]);
Mat imgLinesEdge;
imgScale.copyTo(imgLinesEdge);
// draw lines after filtering
for (int i = 0, steps = lineEdge.size(); i < steps; i++)
{
line(imgLinesEdge, Point(lineEdge[i][0], lineEdge[i][1]), Point(lineEdge[i][2], lineEdge[i][3]), Scalar(0, 0, 255), 3, 8);
}
imshow("PCB edge lines", imgLinesEdge);
for (int i = 0, step = lineEdge.size(); i < step; i++) // calcualte averge tilt angle of PCB edge lines
{
correctAngle += angleForCorrect(lineEdge[i]);
}
correctAngle /= lineEdge.size();
}
Mat dst;
rotateIamge(imgOri, dst, correctAngle);
imshow("rotated", dst);
}
运行结果展示: