1. 程式人生 > >OpenCV基於傅立葉變換以及霍夫直線檢測的旋轉文字校正

OpenCV基於傅立葉變換以及霍夫直線檢測的旋轉文字校正

最近剛好結束了霍夫三部曲以及離散傅立葉變換的總結,剛好了解到它們兩個的結合可以實現一個很有意思的功能

旋轉文字影象的校正,於是參考了幾篇部落格,記錄下來。


主要參考部落格

標準霍夫直線檢測 以及影象的傅立葉變換

關於傅立葉變換的原理請看我的上一篇部落格,也是為這篇文章做了個鋪墊

一幅文字影象


對影象進行旋轉矯正,關鍵是要獲取旋轉角度是多少!獲取了旋轉角度就可以用仿射變換對影象進行矯正,影象旋轉的程式碼下面會貼出。

旋轉角度怎麼獲取?可以對影象作傅立葉變換獲取這個角度,有了上篇文章的理論基礎,相信不難理解

文字影象的明顯特徵就是存在分行間隔,那麼行與文字之間這個灰度值變化就不如真正的文字及文字間的變化劇烈,那麼相應的這些地方的頻譜值也低,即頻譜的低譜部分,因為傅立葉變換就是表徵影象各點的變化頻率的嘛~當文字影象旋轉時,基頻域中的頻譜也會隨之改變,那麼我就可以根據這一特點來計算這個角度。

Samples:

/*
 *  Author: John Hany
 *  Website: http://johnhany.net
 *  Source code updates: https://github/johnhany/textRotCorrect
 *  If you have any advice, you could contact me at: [email protected]
 *  Need OpenCV environment!
 *
 */

#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <iostream>

using namespace cv;
using namespace std;

#define GRAY_THRESH 150
#define HOUGH_VOTE 100

//#define DEGREE 27

int main(int argc, char **argv)
{
    //Read a single-channel image
    const char* filename = "imageText_02_R.jpg";
    Mat srcImg = imread(filename, CV_LOAD_IMAGE_GRAYSCALE);
    if(srcImg.empty())
        return -1;
    imshow("source", srcImg);

    Point center(srcImg.cols/2, srcImg.rows/2);

#ifdef DEGREE
    //Rotate source image
    Mat rotMatS = getRotationMatrix2D(center, DEGREE, 1.0);
    warpAffine(srcImg, srcImg, rotMatS, srcImg.size(), 1, 0, Scalar(255,255,255));
    imshow("RotatedSrc", srcImg);
    //imwrite("imageText_R.jpg",srcImg);
#endif

    //Expand image to an optimal size, for faster processing speed
    //Set widths of borders in four directions
    //If borderType==BORDER_CONSTANT, fill the borders with (0,0,0)
    Mat padded;
    int opWidth = getOptimalDFTSize(srcImg.rows);
    int opHeight = getOptimalDFTSize(srcImg.cols);
    copyMakeBorder(srcImg, padded, 0, opWidth-srcImg.rows, 0, opHeight-srcImg.cols, BORDER_CONSTANT, Scalar::all(0));

    Mat planes[] = {Mat_<float>(padded), Mat::zeros(padded.size(), CV_32F)};
    Mat comImg;
    //Merge into a double-channel image
    merge(planes,2,comImg);

    //Use the same image as input and output,
    //so that the results can fit in Mat well
    dft(comImg, comImg);

    //Compute the magnitude
    //planes[0]=Re(DFT(I)), planes[1]=Im(DFT(I))
    //magnitude=sqrt(Re^2+Im^2)
    split(comImg, planes);
    magnitude(planes[0], planes[1], planes[0]);

    //Switch to logarithmic scale, for better visual results
    //M2=log(1+M1)
    Mat magMat = planes[0];
    magMat += Scalar::all(1);
    log(magMat, magMat);

    //Crop the spectrum
    //Width and height of magMat should be even, so that they can be divided by 2
    //-2 is 11111110 in binary system, operator & make sure width and height are always even
    magMat = magMat(Rect(0, 0, magMat.cols & -2, magMat.rows & -2));

    //Rearrange the quadrants of Fourier image,
    //so that the origin is at the center of image,
    //and move the high frequency to the corners
    int cx = magMat.cols/2;
    int cy = magMat.rows/2;

    Mat q0(magMat, Rect(0, 0, cx, cy));
    Mat q1(magMat, Rect(0, cy, cx, cy));
    Mat q2(magMat, Rect(cx, cy, cx, cy));
    Mat q3(magMat, Rect(cx, 0, cx, cy));

    Mat tmp;
    q0.copyTo(tmp);
    q2.copyTo(q0);
    tmp.copyTo(q2);

    q1.copyTo(tmp);
    q3.copyTo(q1);
    tmp.copyTo(q3);

    //Normalize the magnitude to [0,1], then to[0,255]
    normalize(magMat, magMat, 0, 1, CV_MINMAX);
    Mat magImg(magMat.size(), CV_8UC1);
    magMat.convertTo(magImg,CV_8UC1,255,0);
    imshow("magnitude", magImg);
    //imwrite("imageText_mag.jpg",magImg);

    //Turn into binary image
    threshold(magImg,magImg,GRAY_THRESH,255,CV_THRESH_BINARY);
    imshow("mag_binary", magImg);
    //imwrite("imageText_bin.jpg",magImg);

    //Find lines with Hough Transformation
    vector<Vec2f> lines;
    float pi180 = (float)CV_PI/180;
    Mat linImg(magImg.size(),CV_8UC3);
    HoughLines(magImg,lines,1,pi180,HOUGH_VOTE,0,0);
    int numLines = lines.size();
    for(int l=0; l<numLines; l++)
    {
        float rho = lines[l][0], theta = lines[l][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));
        line(linImg,pt1,pt2,Scalar(255,0,0),3,8,0);
    }
    imshow("lines",linImg);
    //imwrite("imageText_line.jpg",linImg);
    if(lines.size() == 3){
        cout << "found three angels:" << endl;
        cout << lines[0][1]*180/CV_PI << endl << lines[1][1]*180/CV_PI << endl << lines[2][1]*180/CV_PI << endl << endl;
    }

    //Find the proper angel from the three found angels
    float angel=0;
    float piThresh = (float)CV_PI/90;
    float pi2 = CV_PI/2;
    for(int l=0; l<numLines; l++)
    {
        float theta = lines[l][1];
        if(abs(theta) < piThresh || abs(theta-pi2) < piThresh)
            continue;
        else{
            angel = theta;
            break;
        }
    }

    //Calculate the rotation angel
    //The image has to be square,
    //so that the rotation angel can be calculate right
    angel = angel<pi2 ? angel : angel-CV_PI;
    if(angel != pi2){
        float angelT = srcImg.rows*tan(angel)/srcImg.cols;
        angel = atan(angelT);
    }
    float angelD = angel*180/(float)CV_PI;
    cout << "the rotation angel to be applied:" << endl << angelD << endl << endl;

    //Rotate the image to recover
    Mat rotMat = getRotationMatrix2D(center,angelD,1.0);
    Mat dstImg = Mat::ones(srcImg.size(),CV_8UC3);
    warpAffine(srcImg,dstImg,rotMat,srcImg.size(),1,0,Scalar(255,255,255));
    imshow("result",dstImg);
    //imwrite("imageText_D.jpg",dstImg);
    
    waitKey(0);

    return 0;
}

過程詳解:

讀取圖片

Mat srcImg = imread(filename, CV_LOAD_IMAGE_GRAYSCALE);
    if(srcImg.empty())
        return -1;

srcImg.empty()用來判斷是否成功讀進影象,如果srcImg中沒有資料,在後面的步驟會產生記憶體錯誤。
由於處理的是文字,彩色資訊不會提供額外幫助,所以要用CV_LOAD_IMAGE_GRAYSCALE表明以灰度形式讀進影象。
假定讀取的影象如下:


旋轉影象(可選)

#ifdef DEGREE
    //Rotate source image
    Mat rotMatS = getRotationMatrix2D(center, DEGREE, 1.0);
    warpAffine(srcImg, srcImg, rotMatS, srcImg.size(), 1, 0, Scalar(255,255,255));
    imshow("RotatedSrc", srcImg);
    //imwrite("imageText_R.jpg",srcImg);
#endif
如果手頭沒有這樣的傾斜影象,可以選擇一張正放的文字影象,再把第12行#define DEGREE那行前的註釋符號去掉。然後這部分程式碼就會把所給的影象旋轉你規定的角度,再交給後面處理。

然後直接就是DFT部分以及霍夫直線檢測部分的結合,這兩部分就不多說了,你懂得,我前面部落格有詳細介紹

提及一下涉及到的兩個引數

兩個引數GRAY_THRESH和HOUGH_VOTE需要手動指定,不同的影象需要設定不同的引數,同一段文字旋轉不同的角度也需要不同的引數。GRAY_THRESH越大,二值化的閾值就越高;HOUGH_VOTE越大,霍夫檢測的投票數就越高(需要更多的共線點來確定一條直線)。說白了,如果發現二值化影象中直線附近有很多散點,就要適當提高GRAY_THRESH;如果發現從二值影象的一條直線上檢測到了幾條角度相差很小的直線,就需要適當提高HOUGH_VOTE。

檢測到的結果:

dft


二值化


霍夫


計算傾斜角
上面得到了三個角度,一個是0度,一個是90度,另一個就是我們所需要的傾斜角。要把這個角找出來,而且要考慮誤差。

//Find the proper angel from the three found angels
    float angel=0;
    float piThresh = (float)CV_PI/90;
    float pi2 = CV_PI/2;
    for(int l=0; l<numLines; l++)
    {
        float theta = lines[l][1];
        if(abs(theta) < piThresh || abs(theta-pi2) < piThresh)
            continue;
        else{
            angel = theta;
            break;
        }
    }

    //Calculate the rotation angel
    //The image has to be square,
    //so that the rotation angel can be calculate right
    angel = angel<pi2 ? angel : angel-CV_PI;
    if(angel != pi2){
        float angelT = srcImg.rows*tan(angel)/srcImg.cols;
        angel = atan(angelT);
    }
    float angelD = angel*180/(float)CV_PI;
    cout << "the rotation angel to be applied:" << endl << angelD << endl << endl;

由於DFT的特點,只有輸入影象是正方形時,檢測到的角才是文字真正旋轉的角度。但我們的輸入影象不一定是正方形的,所以要根據影象的長寬比改變這個角度。這個地方望高手給出解答
        還有一個需要注意的細節,雖然HoughLines()輸出的傾斜角在[0,180)之間,但在[0,90]和(90,180)之間這個角的含義是不同的。請看圖示:


當傾斜角大於90度時,(180-傾斜角)才是直線相對豎直方向的偏離角度。在OpenCV中,逆時針旋轉,角度為正。要把影象轉回去,

這個角度就變成了(傾斜角-180)。

傾斜角:


校正影象

Mat rotMat = getRotationMatrix2D(center,angelD,1.0);
    Mat dstImg = Mat::ones(srcImg.size(),CV_8UC3);
    warpAffine(srcImg,dstImg,rotMat,srcImg.size(),1,0,Scalar(255,255,255));

先用getRotationMatrix2D()獲得一個2*3的仿射變換矩陣,再把這個矩陣輸入warpAffine(),做一個單純旋轉的仿射變換。warpAffine()

的最後一個引數Scalar(255,255,255)是把由於旋轉產生的空白用白色填充

最後結果:



可以很明顯的看出來清晰度下降了不少,感覺還是由能量損失(不知道啥原因)

至於其他一些文字的情況,大家可以自行檢驗

相關推薦

OpenCV基於變換以及直線檢測旋轉文字校正

最近剛好結束了霍夫三部曲以及離散傅立葉變換的總結,剛好了解到它們兩個的結合可以實現一個很有意思的功能 旋轉文字影象的校正,於是參考了幾篇部落格,記錄下來。 主要參考部落格: 標準霍夫直線檢測 以及影象的傅立葉變換 關於傅立葉變換的原理請看我的上一篇部落格,也是為這篇文章

OpenCV—python 影象矯正(基於變換基於透視變換

影象校正執行環境 Anaconda| pycharm/jupyter notebook 你需要安裝如下庫 pip install numpy pip install matplotlib pip install opencv-python 一、基於傅立葉變換

opencv變換 FFT

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

二維離散變換以及濾波應用

一、二維離散傅立葉變換 二維離散傅立葉變換的公式:F(u,v)=1MN[∑m=0M−1∑n=0N−1f(m,n)WMumWNvn]∙RMN(u,v) F(u,v) = \frac{1}{MN}[\sum_{m=0}^{M-1}\sum_{n=0}^{N-1}f

影象處理中變換以及頻率域影象增強詳解

岡薩雷斯版<影象處理>裡面的解釋非常形象:一個恰當的比喻是將傅立葉變換比作一個玻璃稜鏡。稜鏡是可以將光分解為不同顏色的物理儀器,每個成分的顏色由波長(或頻率)來決定。傅立葉變換可以看作是數學上的稜鏡,將函式基於頻率分解為不同的成分。當我們考慮光時,討論它的光譜

基於變換和PyQt4開發一個簡單的頻率計數器

小學期的《訊號與系統》課,要求寫一個頻率計數器,下面是我個人理解的頻率計數 傅立葉變換的程式碼: # coding=utf-8 import numpy as np from scipy.io import wavfile import matplotlib.mlab as mlab import matp

opencv實現變換

const char* filename = argc >=2 ? argv[1] : "lena.jpg"; Mat I = imread(filename, CV_LOAD_IMAGE_GRAYSCALE); 原始影象: 獲

OpenCv-C++-小案例實戰-直線檢測以及直線檢測程式碼)

對於一份試卷,我現在需要檢測到填空題上面的橫線。如下圖: 很多人第一反應是霍夫直線檢測,包括我也是想到用霍夫直線檢測。然而事實並不盡如人意。 因為在我的部落格中並沒有放上霍夫直線檢測這一部分,所以,我用霍夫直線演算法來檢測試卷上的橫線。 霍夫直線檢測: #include<op

0022-在OpenCV環境下做影象或矩陣的變換

傅立葉變換的概念在《高等數學》、《訊號與系統》、《數字訊號處理》中都有詳細的原理說明,網上也有一大堆文章解釋其原理。這裡我就不多說了,總之它是把訊號變換到三角函式系裡,實際上是域的變換,至於變換有什麼好處,其實就是從另一個角度觀察同一個訊號。對影象的傅立葉變換實際上是一個二維傅立葉變換。OpenCV

基於python的快速變換FFT(二)

基於python的快速傅立葉變換FFT(二)本文在上一篇部落格的基礎上進一步探究正弦函式及其FFT變換。 知識點  FFT變換,其實就是快速離散傅立葉變換,傅立葉變換是數字訊號處理領域一種很重要的演算法。要知道傅立葉變換演算法的意義,首先要了解傅立葉原理的意義。傅立葉原理表明:任何連續測量的時序或訊號,都可

【GCN】圖卷積網路初探——基於圖(Graph)的變換和卷積

本文為從CNN到GCN的聯絡與區別——GCN從入門到精(fang)通(qi)的閱讀筆記,文中絕大部分公式和圖片摘自原文。 文章目錄 一、CNN(卷積神經網路)中的離散卷積 二、GCN基本概念介紹 (一)圖Grap

變換及其在opencv中影象去噪的實現

前言 我保證這篇文章和你以前看過的所有文章都不同,這是12年還在果殼的時候寫的,但是當時沒有來得及寫 完就出國了……於是拖了兩年,嗯,我是拖延症患者…… 這篇文章的核心思想就是: 要讓讀者在不看任何數學公式的情況下理解傅立葉分析。 傅立葉分析不僅僅是一個數學工

opencv 變換及其逆變換例項及其理解

傅立葉變換是把影象從空間域轉化到頻率域的變換。 空間域 一般的情況下,空間域的影象是f(x,y)=灰度級(0-255),形象一點就是一個二維矩陣,每個座標對應一個顏色值。 頻率域 先介紹幾個概念 頻率:對於影象來說可以指影象顏色值的梯度,即灰度級的變化速度 幅度:可以簡單的理

分別用OpenCV-Python和Numpy實現變換和逆變換

Numpy實現 fft = np.fft.fft2(img) 將空間域轉化為頻率域 OpenCV實現 dft = cv2.dft(np.float32(img),flag=cv2.DFT_COMPLEX_OUTPUT) 這個函式與np.fft.fft2(img)實現相同的功能,但要注意先

OpenCV中對影象進行二維離散變換

#include<opencv2/opencv.hpp> #include <highgui.h> #include <iostream> #include <cv.h> #include <opencv2/core/c

OpenCV學習筆記(六)離散變換

離散傅立葉變換: 傅立葉變換將講時域訊號分解為不同頻率的正弦訊號或餘弦訊號疊加之和,時域分析只能反映訊號的幅值隨時間變化得情況,除單頻率分量的簡諧波外,很難對資訊頻率的組成及各頻率分量的大小進行詳細分析,而訊號頻譜分析提供了比時域訊號波形更直觀、更豐富的資訊。在實際的影象處

基於二維變換法的MRI成像原理的Matlab模擬(3)

三、PDWI、T1WI、T2WI模擬         與CT相比,MRI影象的重建要簡單得多。但是,MRI K空間資料的獲取過程要比CT複雜,其可調整的實際物理引數眾多,這也使得MRI能夠提供不同加權的影象,得到十分豐富的影象資訊。本節我們將模擬PDWI、T1WI、T2WI

opencv 中 快速變換 FFT

opencv 中 傅立葉變換 FFT,程式碼如下: void fft2(IplImage *src, IplImage *dst) { //實部、虛部 IplImage *image_Re = 0, *image_Im = 0, *Fourier = 0; //

Python下opencv使用筆記(十)(影象頻域濾波與變換

前面曾經介紹過空間域濾波,空間域濾波就是用各種模板直接與影象進行卷積運算,實現對影象的處理,這種方法直接對影象空間操作,操作簡單,所以也是空間域濾波。 頻域濾波說到底最終可能是和空間域濾波實現相同的功能,比如實現影象的輪廓提取,在空間域濾波中我們使用一個拉普拉

Python中二維快速變換----基於numpy庫

二維傅立葉變換在影象處理中經常用到,為了更好理解python中的fft2。這裡我們生成了二維正弦條紋,然後進行快速傅立葉變換。 #Python版本:Python3.5 #用到的庫:numpy,matploylib #作者:James_Ray_Murphy # -*- co