校正图片样例:
上图1\2\3分别为常见的几种需要倾斜校正处理的样例。
对于不同样式的图片倾斜校正的思路是不同的:
图1中边界信息明显,可以通过findcontours函数检测轮廓矩阵获得角度从而进行旋转校正。
图2中没有明显边界,但是每行文字信息比较明显,因此可以通过canny函数进行边缘检测,根据一行一行的文字信息确定角度从而进行校正。
图3是真实场景中随意的一张表单照片(重要信息打码啦~~),图片中的表单没有明显的轮廓信息(人类随手拍的),图像文字多为短小、不连续噪音较多,但是笔直的表格线是可以看到的,因此采用检测表格线的方式进行校正。
总体思路:
1、图像预处理
2、用霍夫线变换检测直线
3、对直线做筛选并对角度做统计
4、角度频率最高的直线的角度做为旋转角度返回。(之后就可以根据这个角度进行旋转啦)
代码实现过程中需要注意的点:
1)图像的预处理(根据样本及需求选择预处理方案)
2)霍夫线变换进行检测,调参,了解每个参数的含义
3)直线的角度、弧度的转换
4)选角度均值或是频率最高的值作为最佳旋转角度(选择最优算法策略)
实现代码如下:
//小角度旋转函数
float get_one_small_angle(cv::Mat &img, int max_angle)//传入图像img,倾斜校正允许的最大旋转角度max_angle
{
cv::Mat m, gray, bi;
float scale_v = resize_img(img, m);
m = img.clone();
//图像预处理(根据需要选择预处理方式)
cv::cvtColor(m, gray, CV_BGR2GRAY);
trans_bright(gray);//转换明亮度
//enhance
imgEnhanceBrightness(gray);//亮度增强
int T_length = gray.cols > gray.rows ? gray.rows : gray.cols;
adaptiveThreshold(gray, bi, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY_INV, 21, 10);
medianBlur(bi,bi,3);
scale_v = resize_img(bi, bi);//二值化
vector<Vec2f> lines;
//霍夫线变换检测直线,第五个参数直线的长度阈值,是大于该阈值则认为是一条直线
HoughLines(bi, lines, 1, CV_PI / 180, 90, 80, 8);
Mat imtest(bi.rows, bi.cols, CV_8UC3, Scalar(255, 255, 255));//构造一个测试图片
std::map<int,int> angle_map;
int cnt = 0;
int max_cnt = 0;
float best_angle=0.0;
int max_iter=300;//检到几十万条线,只取300个,否则循环太慢
if(lines.size()<300)
max_iter = lines.size();
for (size_t i = 0; i < max_iter; i++)
{
float rho = lines[i][0];
float theta = lines[i][1];
Point pt1, pt2;
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));
int length_pt = disPt2Pt(pt1,pt2);
if(length_pt < (T_length * 0.05)) continue;
Point pt_mid((pt1.x+pt2.x)/2,(pt1.y+pt2.y)/2);
//弧度角度注意转换
float ang_ = atan2((pt2.y-pt1.y)*1.0, (pt2.x-pt1.x)*1.0);
float theta1 = CV_PI/2 + ang_;
float fangle = theta1 / CV_PI * 360;
fangle = round(180 - fangle);//角度四舍五入
if(abs(fangle)<max_angle && pt_mid.y>bi.rows*0.15 && pt_mid.y < bi.rows*(1-0.15))
{//直线的角度小于最大倾斜角,且直线既不偏上又不偏下
cnt++;
cv::line(imtest, pt1, pt2, Scalar(0,0,0),2);//在imtest上画线
if(angle_map.end() == angle_map.find(fangle))
{
angle_map[fangle] = 1;//map中没有这个角度,则记为1
}
else//若角度存在,则+1,并且将频率最高的角度记为最佳角度
{
angle_map[fangle]++;
if(angle_map[fangle]>max_cnt){
max_cnt = angle_map[fangle];
best_angle = fangle;
}
}
}
// imshow("linesImg", imtest);
}
return best_angle;
}
参考: