文档图像倾斜校正算法(1)——文本行投影法

文档图像倾斜校正算法(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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章