霍夫变换(Hough Transform)的主要思想:
-
OpenCV的霍夫变换(Hough Transform)直线检测
一条直线在平面直角坐标系(x-y)中可以用y=ax+b式表示,对于直线上一个确定的点(x0,y0),总符合y0-ax0=b,而它可以表示为参数平面坐标系(a-b)中的一条直线。因此,图像中的一个点对应参数平面的一条直线,同样,图像中的一条直线对应参数平面上的一个点。
基本Hough变换检测直线:
由于同一条直线上的不同点在参数平面中是会经过同一个点的多条线。对图像的所有点作霍夫变换,检测直线就意味着找到对应参数平面中的直线相交最多的点。对这些交点做票数累计,然后取出票数大于最小投票数的点,即为原坐标系里检测出的直线。
一般,直线的参数方程为 ρ=xcosθ+ysinθ
OpenCV中的基本霍夫变换直线检测函数 cv::HoughLines:
函数输入为一幅二值图像(有很多待检测点),其中一些点排列后形成直线,通常这是一幅边缘图像,比如来自Sobel算子或Canny算子。函数的输出是cv::Vec2f的向量,每个元素都是一对代表检测到的直线的浮点数(ρ, θ)。函数的作法是先求出原图像中每点的极坐标方程,若相交于一点的极坐标曲线的个数大于最小投票数,则将该点(ρ, θ)(参数坐标系点)放入输出向量。
#include "opencv2/highgui.hpp"
#include "opencv2/core.hpp"
#include "opencv2/imgproc.hpp"
#include <iostream>
#define PI 3.1415926
class LineFinder{
private:
std::vector<cv::Vec2f> lines;
double deltaRho; // 参数坐标系的步长(theta表示与直线垂直的角度)
double deltaTheta;
int minVote; // 判断是直线的最小投票数
public:
LineFinder() {
deltaRho = 1;
deltaTheta = PI / 180;
minVote = 80;
}
void setAccResolution(double dRho, double dTheta) {
deltaRho = dRho;
deltaTheta = dTheta;
}
void setMinVote(int minv) {
minVote = minv;
}
// Hough变换检测直线;rho=1,theta=PI/180参数坐标系里的步长,threshold=最小投票数
void findLines(cv::Mat& binary){
lines.clear();
cv::HoughLines(binary, lines, deltaRho, deltaTheta, minVote);
}
void drawDetectedLines(cv::Mat& result){
std::vector<cv::Vec2f>::const_iterator it = lines.begin();
while (it != lines.end())
{
// 以下两个参数用来检测直线属于垂直线还是水平线
float rho = (*it)[0];
float theta = (*it)[1];
if (theta < PI / 4. || theta > 3.*PI / 4.)
{ // 若检测为垂直线,直线交于图片的上下两边,先找交点
cv::Point pt1(rho / cos(theta), 0);
cv::Point pt2((rho - result.rows*sin(theta)) / cos(theta), result.rows);
cv::line(result, pt1, pt2, cv::Scalar(255), 1); //
}
else // 若检测为水平线,直线交于图片的左右两边,先找交点
{
cv::Point pt1(0, rho / sin(theta));
cv::Point pt2(result.cols, (rho - result.cols*cos(theta)) / sin(theta));
cv::line(result, pt1, pt2, cv::Scalar(255), 1);
}
++it;
}
}
};
int main(int argc, char *argv[])
{
cv::Mat image = cv::imread("D:/VS_exercise/images/road1.jpg");
cv::Mat imageGray;
cv::Mat contours;
cv::cvtColor(image, imageGray, cv::COLOR_RGB2GRAY);
cv::Canny(imageGray, contours, 190, 300);
// 在原图的拷贝上画直线
cv::Mat result(contours.rows, contours.cols, CV_8U, cv::Scalar(255));
image.copyTo(result);
// Hough变换检测
LineFinder finder;
finder.setMinVote(130);
finder.findLines(contours);
finder.drawDetectedLines(result);
// 显示
cv::namedWindow("Detected Lines with Hough");
cv::imshow("Detected Lines with Hough", result);
cv::waitKey(0);
return 0;
}
概率Hough变换检测线段:
标准霍夫变换本质上是把图像映射到它的参数空间上,它需要计算所有的M个边缘点,这样它的运算量和所需内存空间都会很大。如果在输入图像中只是处理m(m<M)个边缘点,则这m个边缘点的选取是具有一定概率性的,因此该方法被称为概率霍夫变换(Probabilistic Hough Transform)。该方法还有一个重要的特点就是能够检测出线端,即能够检测出图像中直线的两个端点,确切地定位图像中的直线。
HoughLinesP函数就是利用概率霍夫变换来检测直线的。它的一般步骤为:
1、随机抽取图像中的一个特征点,即边缘点,如果该点已经被标定为是某一条直线上的点,则继续在剩下的边缘点中随机抽取一个边缘点,直到所有边缘点都抽取完了为止;
2、对该点进行霍夫变换,并进行累加和计算;
3、选取在霍夫空间内值最大的点,如果该点大于阈值的,则进行步骤4,否则回到步骤1;
4、根据霍夫变换得到的最大值,从该点出发,沿着直线的方向位移,从而找到直线的两个端点;
5、计算直线的长度,如果大于某个阈值,则被认为是好的直线输出,回到步骤1。
HoughLinesP函数的原型为:
void HoughLinesP(InputArray image,OutputArray lines, double rho, double theta, int threshold, double minLineLength=0,double maxLineGap=0 )
image为输入图像,要求是8位单通道图像
lines为输出的直线向量,每条线用4个元素表示,即直线的两个端点的4个坐标值
rho和theta分别为距离和角度的分辨率
threshold为阈值,即步骤3中的阈值
minLineLength为最小直线长度,在步骤5中要用到,即如果小于该值,则不被认为是一条直线
maxLineGap为最大直线间隙,在步骤4中要用到,即如果有两条线段是在一条直线上,但它们之间因为有间隙,所以被认为是两个线段,如果这个间隙大于该值,则被认为是两条线段,否则是一条。
霍夫变换检测直线的目的,是找到二值图像中经过足够多数量点的所有直线,当同一直线穿过许多点,便意味着这条线的存在足够明显。
概率霍夫变换在原算法的基础上增加了一些改动,主要是:
1. 不再系统地逐行扫描图像,而是随机挑选(轮廓图像的)前景点,一旦累加器中的某一项交点的票数达到给定的最小值,就搜索轮廓图像在对应直线上的前景点,连成线段(要小于maxLineGap),然后记录线段参数(起终点),最后删除所有经过的点(即使它们并未投过票)。
2. 概率霍夫变换定义了两个额外的参数:一个是可以接受的最小线段长度(minLineLength),另一个是允许组成连续线段的最大像素间隔(maxLineGap),虽然额外步骤增加了算法的复杂度,但由于参与投票的点数有所减少,因此得到了一些补偿。
openCV中的概率霍夫变换直线检测函数 cv::HoughLinesP:
函数的输出是cv::Vec4i组成的向量,每个元素是检测到的线段的两个坐标点(pt1x, pt1y, pt2x, pt2y)。
#include "opencv2/highgui.hpp"
#include "opencv2/core.hpp"
#include "opencv2/imgproc.hpp"
#define PI 3.1415926
class LineFinder{
private:
std::vector<cv::Vec4i> lines;
double deltaRho; // 步长(theta表示与直线垂直的角度)
double deltaTheta;
int minVote; // 判断是直线的最小投票数
double minLength; // 判断是直线的最小线段长度
double maxGap; // 允许组成连续线段的最大像素间隔
public:
LineFinder() {
deltaRho = 1;
deltaTheta = PI / 180;
minVote = 10;
minLength = 0.0;
maxGap = 0.0;
}
void setAccResolution(double dRho, double dTheta) {
deltaRho = dRho;
deltaTheta = dTheta;
}
void setMinVote(int minv) {
minVote = minv;
}
void setLineLengthAndGap(double length, double gap) {
minLength = length;
maxGap = gap;
}
// Hough变换检测线段
void findLines(cv::Mat& binary) {
lines.clear();
cv::HoughLinesP(binary, lines, deltaRho, deltaTheta, minVote, minLength, maxGap);
}
void drawDetectedLines(cv::Mat &image, cv::Scalar color = cv::Scalar(255)) {
std::vector<cv::Vec4i>::const_iterator it2 = lines.begin();
while (it2 != lines.end()) {
cv::Point pt1((*it2)[0], (*it2)[1]);
cv::Point pt2((*it2)[2], (*it2)[3]);
cv::line(image, pt1, pt2, color, 1.5); //画线段
++it2;
}
}
};
int main(int argc, char *argv[])
{
cv::Mat image = cv::imread("D:/VS_exercise/images/road1.jpg");
cv::Mat imageGray;
cv::Mat contours;
cv::cvtColor(image, imageGray, cv::COLOR_RGB2GRAY);
// 边缘检测
cv::Canny(imageGray, contours, 190, 300);
// Hough变换检测
LineFinder finder;
finder.setMinVote(80);
finder.setLineLengthAndGap(100, 10); //概率Hough变换增加的两个参数
finder.findLines(contours);
finder.drawDetectedLines(image);
// 显示
cv::imshow("Detected Lines with Hough", image);
cv::waitKey(0);
return 0;
}
-
如何测量白色区域的宽度
方法如下:可以将图像看成一个二维数组,从第一行开始,找到第一行从黑色转化成白色的点的横坐标,再去找第一行从白色转化成黑色的点的横坐标。两者之差就是图像的宽度。第一行处理完毕,继续处理第二第三行等等。思想很简单,代码不长,也不做过多的解释。
#include "stdio.h"
#include "cv.h"
#include "highgui.h"
#include <cxcore.hpp>
#include <afxcom_.h>
using namespace cv;
using namespace std;
int main()
{
IplImage* src;
src=cvLoadImage("bin.jpg",CV_LOAD_IMAGE_GRAYSCALE);
cvThreshold( src, src,80, 255, CV_THRESH_BINARY );//二值化
int width=src->width;
int height=src->height;
BOOL bFirst=FALSE;
int x1,x2;
for (int j=1;j<height;++j)
{
for (int i=1;i<width;++i)
{
int tempElem;
tempElem=CV_IMAGE_ELEM(src,byte,j,i);
ASSERT(tempElem==0||tempElem==255);
if (CV_IMAGE_ELEM(src,byte,j,i)>0&&CV_IMAGE_ELEM(src,byte,j,i-1)==0)
{
x1=i;
}
if (CV_IMAGE_ELEM(src,byte,j,i)==0&&CV_IMAGE_ELEM(src,byte,j,i-1)>0)
{
x2=i;
printf("x1=%d, x2=%d ,y=%d ,Image width=%d \n",x1,x2,j,x2-x1);
}
}
}
cvNamedWindow( "Source", 1 );
cvShowImage( "Source", src );
cvWaitKey(0);
cvDestroyWindow( "Source" );
cvReleaseImage(&src);
return 0;
}
测试结果图片如下。
opencv提供了line()函数来对直线的绘制。其原型如下:
-
opencv line 绘制直线
void line(Mat& img, Point pt1, Point pt2, const Scalar& color, int thickness=1, int lineType=8, int shift=0)
参数:
img: 要绘制线段的图像。
pt1: 线段的起点。
pt2: 线段的终点。
color: 线段的颜色,通过一个Scalar对象定义。
thickness: 线条的宽度。
lineType: 线段的类型。可以取值8, 4, 和CV_AA, 分别代表8邻接连接线,4邻接连接线和反锯齿连接线。默认值为8邻接。为了获得更好地效果可以选用CV_AA(采用了高斯滤波)。
shift: 坐标点小数点位数。
示例代码:
#include <iostream>
#include <stdio.h>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
int main()
{
Mat src=imread("A.jpg");
line(src,Point(1,1),Point(250,250),Scalar(0,0,255),5,CV_AA);
imwrite("src.jpg",src);
imshow("A",src);
printf("channel: %d",src.channels());
#if 0
Mat dst;
Canny(src,dst,50,200);
imwrite("dst.jpg",dst);
imshow("dst",dst);
#endif
waitKey(0);
}
-
分别检测两线段的长度并算出这两平行线之间的距离
将图像转换成灰度图像->霍夫变换->将检测直线的端点(绿色圆圈)标记出来
得到下面的结果:
可以看到,我们检测到了很多条直线,如图有很多直线的端点,尤其是直线端点处,被检测到了多个端点(可以看得出有重叠效果)。其实也不难理解,这条直线在像素上来说是有厚度的,所以难免被检测到多条直线,这样的话就带来了问题,我们无法确定到底哪个是真正直线的两端点,于是就不能够得到线段的长度!那么我们在使用霍夫变换前需要做一些处理。-----------图像细化。
图像细化实际上就是图像骨架化,把图像核心的骨架留下,其他的赘余点删除,对于这个图来说就是把这条直线的厚度消除,得到一条单像素厚度的直线段。实际上,图像细化在图像处理、模式识别中使用的非常广发,也是非常关键的过程,这里我们介绍一种比较经典实用的图像细化方法----Zhang并行快速算法:
具体实现方法、步骤如下:
首先定义如下的8领域(注意顺序不能变):
其中P1是我们讨论的像素点,首先前提条件是P1是前景点!若要删除该点,需要满足一下条件:
过程1:
(1)2 <= N(P1) <= 6,N(x)为x的8领域中黑点(背景点)的个数;
(2)A(P1) = 1,A(x)为P2-P8之间按序前后分别为0、1的对数;
(3)P2*P4*P6 = 0;
(4)P4*P6*P8 = 0.
满足上述四个条件,可将P1点去除;
过程2:
(1)2 <= N(P1) <= 6,N(x)为x的8领域中黑点(背景点)的个数;
(2)A(P1) = 1,A(x)为P2-P8之间按序前后分别为0、1的对数;
(3)P2*P4*P8 = 0;
(4)P2*P6*P8 = 0.
满足上述四个条件,可将P1点去除;
以上即为Zhang并行算法的主要步骤,需要反复运行直到无法得到可删除点为止。对于图像细化,这里只做一个简单的了解,很多理论知识以及高级的算法我后续会再进行分析。
给出关键代码函数如下:
//寻找按序的前后分别是0、1的对数函数
int Findn(IplImage* img, int i, int j)
{
CvScalar s1 = cvGet2D(img, i, j);
CvScalar s2 = cvGet2D(img, i - 1, j);
CvScalar s3 = cvGet2D(img, i - 1, j + 1);
CvScalar s4 = cvGet2D(img, i, j + 1);
CvScalar s5 = cvGet2D(img, i + 1, j + 1);
CvScalar s6 = cvGet2D(img, i + 1, j);
CvScalar s7 = cvGet2D(img, i + 1, j - 1);
CvScalar s8 = cvGet2D(img, i, j - 1);
CvScalar s9 = cvGet2D(img, i - 1, j - 1);
int a = s1.val[0];
int b = s2.val[0];
int c = s3.val[0];
int d = s4.val[0];
int e = s5.val[0];
int f = s6.val[0];
int g = s7.val[0];
int h = s8.val[0];
int l = s9.val[0];
int find[] = { a, b, c, d, e, f, g, h, l };//按8领域顺序定义数组,方便操作
int n = 0;
for (int x = 2; x < 9; ++x)
{
if (find[x] == 0 && find[x + 1] == 255)
{
n = n + 1;
}
}
return n;
}
//这是细化直线的功能函数
IplImage* ThinImage(IplImage* img, int k) //确保输入的是二值图像
{
int condition = 0;//满足的条件个数
int mark = 0;//成功的标志位
int firstN = 0;//第一个条件黑点的个数
CvScalar s;
for (int n = 0; n < k; ++n)
{
for (int i = 1; i < img->height - 1; ++i)
{
for (int j = 1; j < img->width - 1;++j)
{//开始过程1的寻找
condition = 0;//初始化条件满足数
//cout << "1" << endl;
s = cvGet2D(img, i, j);
if (s.val[0] == 255)//如果这是前景点,即边缘
{
//cout << "2" << endl;
//*************************第一过程****************************
//*************************step1****************************
firstN = 0;
for (int ii = -1; ii < 1; ++ii)
{
for (int jj = -1; jj < 1; ++jj)
{
s = cvGet2D(img, i + ii, j + jj);
if (s.val[0] == 255)
{
firstN = firstN + 1;
}
}
}
if (firstN < 3 || firstN > 7)
{
continue;
}
else
{
condition = condition + 1;
}
//****************************************************************
//*************************step2*********************************
if (Findn(img, i, j) != 1)
{
continue;
}
else
{
condition = condition + 1;
}
//****************************************************************
//*************************step3*********************************
CvScalar s1 = cvGet2D(img, i - 1, j);
CvScalar s2 = cvGet2D(img, i, j + 1);
CvScalar s3 = cvGet2D(img, i + 1, j);
CvScalar s4 = cvGet2D(img, i, j - 1);
int a = s1.val[0];//2
int b = s2.val[0];//4
int c = s3.val[0];//6
int d = s4.val[0];//8
if (a * b * c != 0)
{
continue;
}
else
{
condition = condition + 1;
}
//*********************************************************************
//******************************step4**********************************
if (b * c * d != 0)
{
continue;
}
else
{
condition = condition + 1;
}
//*********************************************************************
//第一过程的结果操作
if (condition == 4)
{
mark = 1;
//((char *)(img->imageData + img->widthStep * (i)))[j] = 0;
CvScalar p;
p.val[0] = 0;
cvSet2D(img, i, j, p);
//cout << "11111111111111111111111111111111111" << endl;
}
}
}
}
//****************************过程2**************************
for (int i = 1; i < img->height - 1; ++i)
{
for (int j = 1; j < img->width - 1;++j)
{//开始过程1的寻找
condition = 0;//初始化条件满足数
//cout << "1" << endl;
s = cvGet2D(img, i, j);
if (s.val[0] == 255)//如果这是前景点,即边缘
{
//cout << "2" << endl;
//*************************第一过程****************************
//*************************step1****************************
firstN = 0;
for (int ii = -1; ii < 1; ++ii)
{
for (int jj = -1; jj < 1; ++jj)
{
s = cvGet2D(img, i + ii, j + jj);
if (s.val[0] == 255)
{
firstN = firstN + 1;
}
}
}
if (firstN < 3 || firstN > 7)
{
continue;
}
else
{
condition = condition + 1;
}
//****************************************************************
//*************************step2*********************************
if (Findn(img, i, j) != 1)
{
continue;
}
else
{
condition = condition + 1;
}
//****************************************************************
//*************************step3*********************************
CvScalar s1 = cvGet2D(img, i - 1, j);
CvScalar s2 = cvGet2D(img, i, j + 1);
CvScalar s3 = cvGet2D(img, i + 1, j);
CvScalar s4 = cvGet2D(img, i, j - 1);
int a = s1.val[0];//2
int b = s2.val[0];//4
int c = s3.val[0];//6
int d = s4.val[0];//8
if (a * b * d != 0)
{
continue;
}
else
{
condition = condition + 1;
}
//*********************************************************************
//******************************step4**********************************
if (a * c * d != 0)
{
continue;
}
else
{
condition = condition + 1;
}
//*********************************************************************
//第一过程的结果操作
if (condition == 4)
{
mark = 1;
//((char *)(img->imageData + img->widthStep * (i)))[j] = 0;
CvScalar p;
p.val[0] = 0;
cvSet2D(img, i, j, p);
//cout << "222222222222222222222222" << endl;
}
}
}
}
//cout << " end " << endl;
}
return img;
}
以上即为图像骨架化的编程实现,让我们看一看效果吧:
可以看到,直线已经骨架化成功了,那么接下来,我们再使用霍夫变换检测线段。使用PPHT方法,得到线段的方向以及范围信息,并将检测的直线端点标记出来,代码如下:
CvMemStorage* storageline = cvCreateMemStorage(0);
CvSeq* lines;
lines = cvHoughLines2(binaryline, storageline, CV_HOUGH_PROBABILISTIC, 1, CV_PI / 300, 100, 10, 1000);
int n = 0;
CvPoint* point;
double temp[8];
for (int i = 0; i < lines->total; ++i)
{
point = (CvPoint*)cvGetSeqElem(lines, i);
temp[i * 4] = point[0].x;
temp[i * 4 + 1] = point[0].y;
temp[i * 4 + 2] = point[1].x;
temp[i * 4 + 3] = point[1].y;
cvLine(resultline, point[0], point[1], CV_RGB(255, 0, 0));
cvCircle(resultline, point[0], 3, CV_RGB(0, 255, 0), 1, 8, 3);
cvCircle(resultline, point[1], 3, CV_RGB(0, 255, 0), 1, 8, 3);
double distance = sqrt((point[1].x - point[0].x) * (point[1].x - point[0].x) + (point[1].y - point[0].y) * (point[1].y - point[0].y));
cout << "the " << i << " line's distance :" << distance << endl;
++n;
}
cout << n << endl;
n = 0;
结果如下:
成功得到了直线端点坐标的信息,并且输出n = 2,即刚刚好检测到两条直线!nice,这样的话直线长度就很好求了,使用数学方法,计算两坐标之间的距离即可。(结果将在后面显示)
第二问实际上在第一问的基础上,就是一个数学问题。
如图,正是求两平行线距离x。
x = |AC|/sin(a)
于是通过向量求得
cos = (AB·AC)/|AB||AC|
sin = sqrt(i - cos^2)
于是便可求得x。
按照如此思路,编程求得上述两问的结果:
-
利用霍夫变换检测直线对图片进行校正
#include<opencv2\opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
#define ERROR 1234
//度数转换
double DegreeTrans(double theta)
{
double res = theta / CV_PI * 180;
return res;
}
//逆时针旋转图像degree角度(原尺寸)
void rotateImage(Mat src, Mat& img_rotate, double degree)
{
//旋转中心为图像中心
Point2f center;
center.x = float(src.cols / 2.0);
center.y = float(src.rows / 2.0);
int length = 0;
length = sqrt(src.cols*src.cols + src.rows*src.rows);
//计算二维旋转的仿射变换矩阵
Mat M = getRotationMatrix2D(center, degree, 1);
warpAffine(src, img_rotate, M, Size(length, length), 1, 0, Scalar(255, 255, 255));//仿射变换,背景色填充为白色
}
//通过霍夫变换计算角度
double CalcDegree(const Mat &srcImage, Mat &dst)
{
Mat midImage, dstImage;
Canny(srcImage, midImage, 50, 200, 3);
cvtColor(midImage, dstImage, CV_GRAY2BGR);
//通过霍夫变换检测直线
vector<Vec2f> lines;
HoughLines(midImage, lines, 1, CV_PI / 180, 300, 0, 0);//第5个参数就是阈值,阈值越大,检测精度越高
//cout << lines.size() << endl;
//由于图像不同,阈值不好设定,因为阈值设定过高导致无法检测直线,阈值过低直线太多,速度很慢
//所以根据阈值由大到小设置了三个阈值,如果经过大量试验后,可以固定一个适合的阈值。
if (!lines.size())
{
HoughLines(midImage, lines, 1, CV_PI / 180, 200, 0, 0);
}
//cout << lines.size() << endl;
if (!lines.size())
{
HoughLines(midImage, lines, 1, CV_PI / 180, 150, 0, 0);
}
//cout << lines.size() << endl;
if (!lines.size())
{
cout << "没有检测到直线!" << endl;
return ERROR;
}
float sum = 0;
//依次画出每条线段
for (size_t i = 0; i < lines.size(); i++)
{
float rho = lines[i][0];
float theta = lines[i][1];
Point pt1, pt2;
//cout << theta << endl;
double a = cos(theta), b = sin(theta);
double x0 = a*rho, y0 = b*rho;
pt1.x = cvRound(x0 + 1000 * (-b));
pt1.y = cvRound(y0 + 1000 * (a));
pt2.x = cvRound(x0 - 1000 * (-b));
pt2.y = cvRound(y0 - 1000 * (a));
//只选角度最小的作为旋转角度
sum += theta;
line(dstImage, pt1, pt2, Scalar(55, 100, 195), 1, CV_AA); //Scalar函数用于调节线段颜色
imshow("直线探测效果图", dstImage);
}
float average = sum / lines.size(); //对所有角度求平均,这样做旋转效果会更好
cout << "average theta:" << average << endl;
double angle = DegreeTrans(average) - 90;
rotateImage(dstImage, dst, angle);
//imshow("直线探测效果图2", dstImage);
return angle;
}
void ImageRecify(const char* pInFileName, const char* pOutFileName)
{
double degree;
Mat src = imread(pInFileName);
imshow("原始图", src);
int srcWidth, srcHight;
srcWidth = src.cols;
srcHight = src.rows;
cout << srcWidth << " " << srcHight << endl;
Mat dst;
src.copyTo(dst);
//倾斜角度矫正
degree = CalcDegree(src, dst);
if (degree == ERROR)
{
cout << "矫正失败!" << endl;
return;
}
rotateImage(src, dst, degree);
cout << "angle:" << degree << endl;
imshow("旋转调整后", dst);
Mat resulyImage = dst(Rect(0, 0, srcWidth, srcHight)); //根据先验知识,估计好文本的长宽,再裁剪下来
imshow("裁剪之后", resulyImage);
imwrite("recified.jpg", resulyImage);
}
int main()
{
ImageRecify("jiao.jpg", "FinalImage.jpg");
waitKey();
return 0;
}
效果图如下所示: