文檔圖像傾斜校正算法(1)——文本行投影法
原理:沿着文本行的方向對文本行進行投影得到的投影值集合,要比其他方向投影值集合的方差要大。
適用範圍:投影求方差的方法適用於圖像局部變形較小,干擾少的文檔圖像。在實現時可以先在圖像中扣取一塊文本行清晰的圖像,再在扣取的小圖上採用該方法進行圖像傾斜校正。
下面代碼是在做火車票識別時的傾斜校正程序,利用二維碼檢測的位置框取一部分文檔圖像,再使用該部分文檔圖像完成傾斜校正(只放了傾斜校正的代碼):
#include <opencv2/opencv.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <vector>
#include <numeric>
#define MY_SKEW 14
//圖像旋轉1:旋轉(截取圖像)Crop
// Mat img :圖像輸入,單通道或者三通道
// Mat & imgout :圖像輸出
// int degree :圖像要旋轉的角度
// int border_value:圖像旋轉填充值
int rotateImage1(Mat img,Mat & imgout, int degree,int border_value)
{
if( img.empty())
return 1;
degree = -degree;//warpAffine默認的旋轉方向是逆時針,所以加負號表示轉化爲順時針
double angle = degree * CV_PI / 180.; // 弧度
double a = sin(angle), b = cos(angle);
int width = img.cols;
int height = img.rows;
int width_rotate = int(width * fabs(b)-height * fabs(a));//height * fabs(a) +
int height_rotate = int(height * fabs(b)-width * fabs(a));//width * fabs(a) +
if(width_rotate<=20||height_rotate<=20)
{
width_rotate = 20;
height_rotate = 20;
}
//旋轉數組map
// [ m0 m1 m2 ] ===> [ A11 A12 b1 ]
// [ m3 m4 m5 ] ===> [ A21 A22 b2 ]
float map[6];
Mat map_matrix = Mat(2, 3, CV_32F, map);
// 旋轉中心
CvPoint2D32f center = cvPoint2D32f(width / 2, height / 2);
CvMat map_matrix2 = map_matrix;
cv2DRotationMatrix(center, degree, 1.0, &map_matrix2);//計算二維旋轉的仿射變換矩陣
map[2] += (width_rotate - width) / 2;
map[5] += (height_rotate - height) / 2;
//Mat img_rotate;
//對圖像做仿射變換
//CV_WARP_FILL_OUTLIERS - 填充所有輸出圖像的象素。
//如果部分象素落在輸入圖像的邊界外,那麼它們的值設定爲 fillval.
//CV_WARP_INVERSE_MAP - 指定 map_matrix 是輸出圖像到輸入圖像的反變換,
int chnnel =img.channels();
if(chnnel == 3)
warpAffine(img, imgout, map_matrix, Size(width_rotate, height_rotate), 1, 0, Scalar(border_value,border_value,border_value));
else
warpAffine(img, imgout, map_matrix, Size(width_rotate, height_rotate), 1, 0, border_value);
return 0;
}
//圖像旋轉2:擴充圖像邊緣full
// Mat img :圖像輸入,單通道或者三通道
// Mat & imgout :圖像輸出
// int degree :圖像要旋轉的角度
// int border_value:圖像旋轉填充值
int rotateImage2(Mat img,Mat & imgout, int degree,int border_value)
{
if(img.empty())
return 1;
degree = -degree;//warpAffine默認的旋轉方向是逆時針,所以加負號表示轉化爲順時針
double angle = degree * CV_PI / 180.; // 弧度
double a = sin(angle), b = cos(angle);
int width = img.cols;
int height = img.rows;
int width_rotate = int(width * fabs(b)+height * fabs(a));//height * fabs(a) +
int height_rotate = int(height * fabs(b)+width * fabs(a));//width * fabs(a) +
if(width_rotate<=20||height_rotate<=20)
{
width_rotate = 20;
height_rotate = 20;
}
//旋轉數組map
// [ m0 m1 m2 ] ===> [ A11 A12 b1 ]
// [ m3 m4 m5 ] ===> [ A21 A22 b2 ]
float map[6];
Mat map_matrix = Mat(2, 3, CV_32F, map);
// 旋轉中心
CvPoint2D32f center = cvPoint2D32f(width / 2, height / 2);
CvMat map_matrix2 = map_matrix;
cv2DRotationMatrix(center, degree, 1.0, &map_matrix2);//計算二維旋轉的仿射變換矩陣
map[2] += (width_rotate - width) / 2;
map[5] += (height_rotate - height) / 2;
//Mat img_rotate;
//對圖像做仿射變換
//CV_WARP_FILL_OUTLIERS - 填充所有輸出圖像的象素。
//如果部分象素落在輸入圖像的邊界外,那麼它們的值設定爲 fillval.
//CV_WARP_INVERSE_MAP - 指定 map_matrix 是輸出圖像到輸入圖像的反變換,
Mat imgout_pro;
int chnnel =img.channels();
if(chnnel == 3)
warpAffine(img, imgout_pro, map_matrix, Size(width_rotate, height_rotate), 1, 0, Scalar(border_value, border_value, border_value));
else
warpAffine(img, imgout_pro, map_matrix, Size(width_rotate, height_rotate), 1, 0, border_value);
resize(imgout_pro, imgout, Size(imgout_pro.rows, imgout_pro.cols), 0, 0, 1);
return 0;
}
//投影傾斜校正:火車票傾斜矯正方法舉例
// const Mat imgin :圖像輸入,三通道
// Mat & imgout :矯正後的圖像輸出
// int &theta :圖像傾斜的角度
int skew_correction_projector(const Mat imgin, Mat & imgout, int &theta)
{
if (imgin.empty())
{
return 1;
}
if (imgin.channels() != 3)
{
return 1;
}
Mat Gray;
cvtColor(imgin, Gray, COLOR_RGB2GRAY);
medianBlur(Gray, Gray, 3);
//imwrite(imagename_out3, code_cropout_resize);
float zoom_ratio = 800.0 / imgin.rows;
resize(Gray, Gray, Size(0, 0), zoom_ratio, zoom_ratio, 1);
Mat Bin;
adaptiveThreshold(Gray, Bin, 255, CV_ADAPTIVE_THRESH_GAUSSIAN_C, CV_THRESH_BINARY, 201, 5.0);
Mat bincut = Bin.clone()
Mat img_skew;
unsigned int matrik2[2 * MY_SKEW + 1];
for (int skew = -MY_SKEW; skew <= MY_SKEW; skew++)
{
rotateImage2(bincut, img_skew, skew, 255);
threshold(img_skew, img_skew, 180, 255, THRESH_BINARY);
Mat img_skew0 = 255 - img_skew;
vector<unsigned int> rows_pro;
unsigned int rows_project;
for (int i = 0; i<img_skew.rows; i++)
{
rows_project = 0;
for (int j = 0; j<img_skew.cols; j++)
{
if (img_skew.at<uchar>(i, j)>0)
rows_project++;
}
rows_pro.push_back(rows_project);
}
double sum = std::accumulate(std::begin(rows_pro), std::end(rows_pro), 0.0);
double mean = sum / rows_pro.size(); //均值
double accum = 0.0;
std::for_each(std::begin(rows_pro), std::end(rows_pro), [&](const double d) {
accum += (d - mean)*(d - mean);
});
double stdev = sqrt(accum / (rows_pro.size() - 1)); //方差
matrik2[skew + MY_SKEW] = stdev;
}
theta = (int)distance(matrik2, max_element(matrik2, matrik2 + sizeof(matrik2) / sizeof(matrik2[0]))) - MY_SKEW;
rotateImage1(imgin, imgout, theta, 0);
return 0;
}