Вычисление перекоса текста OpenCV

Я пытаюсь рассчитать перекос текста на изображении, чтобы исправить его для достижения наилучших результатов распознавания.

В настоящее время это функция, которую я использую:

double compute_skew(Mat &img)
{

    // Binarize
    cv::threshold(img, img, 225, 255, cv::THRESH_BINARY);

    // Invert colors
    cv::bitwise_not(img, img);

    cv::Mat element = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(5, 3));
    cv::erode(img, img, element);

    std::vector<cv::Point> points;
    cv::Mat_<uchar>::iterator it = img.begin<uchar>();
    cv::Mat_<uchar>::iterator end = img.end<uchar>();
    for (; it != end; ++it)
        if (*it)
            points.push_back(it.pos());

    cv::RotatedRect box = cv::minAreaRect(cv::Mat(points));

    double angle = box.angle;
    if (angle < -45.)
        angle += 90.;

    cv::Point2f vertices[4];
    box.points(vertices);
    for(int i = 0; i < 4; ++i)
        cv::line(img, vertices[i], vertices[(i + 1) % 4], cv::Scalar(255, 0, 0), 1, CV_AA);

    return angle;
}

Когда я смотрю на этот угол в отладке, я получаю 0.000000 введите здесь описание изображения

Однако, когда я даю ему это изображение, я получаю правильные результаты перекоса около 16 градусов:

введите здесь описание изображения

Как я могу правильно определить перекос на первом изображении?


person Clip    schedule 04.06.2014    source источник


Ответы (2)


есть несколько других способов получить степень перекоса: 1) с помощью преобразования Хафа 2) с помощью профиля горизонтальной проекции. повернуть изображение под разными углами и вычислить горизонтальную проекцию. угол с наибольшим горизонтальным значением гистограммы является углом устранения перекоса.

я представил ниже реализацию 1). я считаю, что это лучше, чем метод бокса, который вы используете, потому что он требует, чтобы вы полностью очистили изображение от любого шума, что просто невозможно в большинстве случаев.

Вы должны знать, что этот метод не работает, если слишком много шума. вы можете уменьшить шум различными способами в зависимости от того, какой тип «линии» вы хотите рассматривать как наиболее доминирующую в изображении. я предоставил два метода для этого. обязательно поиграйтесь с параметрами и порогом и т. д.

результаты (все выполняются с использованием preprocess2, все выполняются с использованием одного и того же набора параметров)

код

#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;

void hough_transform(Mat& im,Mat& orig,double* skew)
{
    double max_r=sqrt(pow(.5*im.cols,2)+pow(.5*im.rows,2));
    int angleBins = 180;
    Mat acc = Mat::zeros(Size(2*max_r,angleBins),CV_32SC1);
    int cenx = im.cols/2;
    int ceny = im.rows/2;
    for(int x=1;x<im.cols-1;x++)
    {
        for(int y=1;y<im.rows-1;y++)
        {
            if(im.at<uchar>(y,x)==255)
            {
                for(int t=0;t<angleBins;t++)
                {
                    double r =(x-cenx)*cos((double)t/angleBins*CV_PI)+(y-ceny)*sin((double)t    /angleBins*CV_PI);
                    r+=max_r;
                    acc.at<int>(t,int(r))++;
                }
            }
        }
    }
    Mat thresh;
    normalize(acc,acc,255,0,NORM_MINMAX);
    convertScaleAbs(acc,acc);
    /*debug
    Mat cmap;
    applyColorMap(acc,cmap,COLORMAP_JET);
    imshow("cmap",cmap);
    imshow("acc",acc);*/

    Point maxLoc;
    minMaxLoc(acc,0,0,0,&maxLoc);
    double theta = (double)maxLoc.y/angleBins*CV_PI;
    double rho = maxLoc.x-max_r;
    if(abs(sin(theta))<0.000001)//check vertical
    {
        //when vertical, line equation becomes
        //x = rho
        double m = -cos(theta)/sin(theta);
        Point2d p1 = Point2d(rho+im.cols/2,0);
        Point2d p2 = Point2d(rho+im.cols/2,im.rows);
        line(orig,p1,p2,Scalar(0,0,255),1);
        *skew=90;
        cout<<"skew angle "<<" 90"<<endl;
    }else
    {
        //convert normal form back to slope intercept form
        //y = mx + b
        double m = -cos(theta)/sin(theta);
        double b = rho/sin(theta)+im.rows/2.-m*im.cols/2.;
        Point2d p1 = Point2d(0,b);
        Point2d p2 = Point2d(im.cols,im.cols*m+b);
        line(orig,p1,p2,Scalar(0,0,255),1);
        double skewangle;
        skewangle= p1.x-p2.x>0? (atan2(p1.y-p2.y,p1.x-p2.x)*180./CV_PI):(atan2(p2.y-p1.y,p2.    x-p1.x)*180./CV_PI);
        *skew=skewangle;
        cout<<"skew angle "<<skewangle<<endl;
    }
    imshow("orig",orig);
}

Mat preprocess1(Mat& im)
{
    Mat ret = Mat::zeros(im.size(),CV_32SC1);

    for(int x=1;x<im.cols-1;x++)
    {
        for(int y=1;y<im.rows-1;y++)
        {

            int gy = (im.at<uchar>(y-1,x+1)-im.at<uchar>(y-1,x-1))
                +2*(im.at<uchar>(y,x+1)-im.at<uchar>(y,x-1))
                +(im.at<uchar>(y+1,x+1)-im.at<uchar>(y+1,x-1));
            int gx = (im.at<uchar>(y+1,x-1) -im.at<uchar>(y-1,x-1))
                +2*(im.at<uchar>(y+1,x)-im.at<uchar>(y-1,x))
                +(im.at<uchar>(y+1,x+1)-im.at<uchar>(y-1,x+1));
            int g2 = (gy*gy + gx*gx);
            ret.at<int>(y,x)=g2;
        }
    }
    normalize(ret,ret,255,0,NORM_MINMAX);
    ret.convertTo(ret,CV_8UC1);
    threshold(ret,ret,50,255,THRESH_BINARY);
    return ret;
}

Mat preprocess2(Mat& im)
{
    // 1) assume white on black and does local thresholding
    // 2) only allow voting top is white and buttom is black(buttom text line)
    Mat thresh;
    //thresh=255-im;
    thresh=im.clone();
    adaptiveThreshold(thresh,thresh,255,CV_ADAPTIVE_THRESH_GAUSSIAN_C,THRESH_BINARY,15,-2);
    Mat ret = Mat::zeros(im.size(),CV_8UC1);
    for(int x=1;x<thresh.cols-1;x++)
    {
        for(int y=1;y<thresh.rows-1;y++)
        {
            bool toprowblack = thresh.at<uchar>(y-1,x)==0 ||  thresh.at<uchar>(y-1,x-1)==0     || thresh.at<uchar>(y-1,x+1)==0;
            bool belowrowblack = thresh.at<uchar>(y+1,x)==0 ||  thresh.at<uchar>(y+1,    x-1)==0 || thresh.at<uchar>(y+1,x+1)==0;

            uchar pix=thresh.at<uchar>(y,x);
            if((!toprowblack && pix==255 && belowrowblack))
            {
                ret.at<uchar>(y,x) = 255;
            }
        }
    }
    return ret;
}
Mat rot(Mat& im,double thetaRad)
{
    cv::Mat rotated;
    double rskew = thetaRad* CV_PI/180;
    double nw = abs(sin(thetaRad))*im.rows+abs(cos(thetaRad))*im.cols;
    double nh = abs(cos(thetaRad))*im.rows+abs(sin(thetaRad))*im.cols;
    cv::Mat rot_mat = cv::getRotationMatrix2D(Point2d(nw*.5,nh*.5), thetaRad*180/CV_PI, 1);
    Mat pos = Mat::zeros(Size(1,3),CV_64FC1);
    pos.at<double>(0)=(nw-im.cols)*.5;
    pos.at<double>(1)=(nh-im.rows)*.5;
    Mat res = rot_mat*pos;
    rot_mat.at<double>(0,2) += res.at<double>(0);
    rot_mat.at<double>(1,2) += res.at<double>(1);
    cv::warpAffine(im, rotated, rot_mat,Size(nw,nh), cv::INTER_LANCZOS4);
    return rotated;
}

int main(int argc, char** argv)
{
    string src="C:/data/skew.png";
    Mat im= imread(src);
    Mat gray;
    cvtColor(im,gray,CV_BGR2GRAY);

    Mat preprocessed = preprocess2(gray);
    imshow("preprocessed2",preprocessed);
    double skew;
    hough_transform(preprocessed,im,&skew);
    Mat rotated = rot(im,skew* CV_PI/180);
    imshow("corrected",rotated);

    waitKey(0);
    return 0;
}
person Zaw Lin    schedule 09.06.2014
comment
Я меняю свое представление о том, что ваши фотографии хороши. Но кода нет. Поверните мои изображения с неправильными углами... - person Can Ürek; 16.08.2014
comment
можешь выложить картинки? может быть, я увижу, можно ли их вообще заставить работать - person Zaw Lin; 16.08.2014
comment
Спасибо за ваш ответ. Можете ли вы помочь мне с обрезкой текста, как этот stackoverflow.com/questions/23125359/ Я не могу реализовать эту ссылку на свои коды. Может быть, после того, как мне нужно снова повернуть. - person Can Ürek; 16.08.2014
comment
хм.. это другая проблема. ответ, который я предоставил, предполагает, что изображение уже обрезано вокруг текста. он занимается только поиском угла поворота. по моему опыту, он лучше всего работает с реальными изображениями с камеры в качестве входных данных.. не так хорошо с синтетическими или уже обработанными изображениями. вы можете настроить функцию preprocess2(Mat& im) в соответствии со своими потребностями. для фактической обрезки изображений вам нужно будет найти другой алгоритм - person Zaw Lin; 18.08.2014
comment
Это лучше, чем это: felix.abecassis.me/2011/09 /opencv-detect-skew-angle Спасибо! - person Martijn Mellens; 22.10.2014
comment
Спасибо, сэр, вы спасаете жизнь - person Ibrahim Amer; 16.04.2015
comment
Большое спасибо. Отлично. Но я боюсь, что нет никакого использования функции preprocess1. Можешь объяснить? Или это ошибка? - person Samitha Chathuranga; 28.07.2015
comment
Это просто альтернатива preprocess2. Вы можете использовать это или использовать preprocess2, и результаты будут разными (возможно, лучше в зависимости от вашего приложения. Для обнаружения текста лучше использовать preprocess2). Обратите внимание, что preprocess1 — это просто стандартный детектор границ. - person Zaw Lin; 28.07.2015
comment
Этот код отличный, но мне интересно, почему угол наклона всегда округляется до ближайшего градуса. - person Nikhil Sridhar; 31.08.2017
comment
Привет, это из-за того, что при вычислении преобразования hough выделяется 180 бинов (по одному бину для каждого угла). Вы можете увеличить его до более высоких ячеек для расчета более точных углов, хотя я сомневаюсь, что это оказывает существенное влияние на производительность. - person Zaw Lin; 31.08.2017
comment
Спасибо, но кажется, что когда вы увеличиваете количество бинов, вы также замедляете время обработки. Любые идеи? - person Nikhil Sridhar; 01.09.2017
comment
Да, это правильно. Производительность квадратична количеству бункеров (я думаю). Я действительно не сосредоточился на производительности. Если вы хотите быстрее, вы можете взглянуть на реализацию opencv, хотя их может быть сложнее понять. Или вы можете взглянуть на реализацию этого с помощью инструкций simd, таких как sse. - person Zaw Lin; 02.09.2017

Опубликованный вами подход имеет собственное предположение об «идеальной бинаризации». пороговое значение напрямую влияет на процесс. используйте порог otsu или подумайте о DFT для универсальное решение.

испытание Оцу:

int main()
{
    Mat input = imread("your text");
    cvtColor(input, input, CV_BGR2GRAY);
    Mat img;
    cv::threshold(input, img, 100, 255, cv::THRESH_OTSU);

    cv::bitwise_not(img, img);
    imshow("img ", img);
    waitKey(0);

    vector<Point> points;
    findNonZero(img, points);
    cv::RotatedRect box = cv::minAreaRect(points);

    double angle = box.angle;
    if (angle < -45.)
        angle += 90.;

    cv::Point2f vertices[4];
    box.points(vertices);
    for(int i = 0; i < 4; ++i)
        cv::line(img, vertices[i], vertices[(i + 1) % 4], cv::Scalar(255, 0, 0));
    imshow("img ", img);
    waitKey(0);

    return 0;
}

введите здесь описание изображения

person baci    schedule 04.06.2014