基于Hu距的圖像旋轉(zhuǎn)矯正之OpenCV實(shí)現(xiàn)

目錄

1系宜、常見(jiàn)圖像旋轉(zhuǎn)矯正方法

1.1 基于圖像邊緣輪廓的旋轉(zhuǎn)矯正

1.2 基于傅里葉變換以及霍夫直線檢測(cè)的旋轉(zhuǎn)矯正

2捏题、基于Hu距圖像旋轉(zhuǎn)矯正

2.1 Hu旋轉(zhuǎn)不變性

2.2 實(shí)現(xiàn)步驟

2.2.1 分別計(jì)算圖像二階距

2.2.2 利用得到的二階距計(jì)算圖像偏轉(zhuǎn)角度

2.2.3 利用仿射變換對(duì)圖像進(jìn)行旋轉(zhuǎn)矯正

2.4 程序?qū)崿F(xiàn)

2.5 旋轉(zhuǎn)矯正效果驗(yàn)證


系統(tǒng)環(huán)境 Windows 10 64 位 + OpenCV 3.4.1 64 位

1、常見(jiàn)圖像旋轉(zhuǎn)矯正方法

常見(jiàn)的圖像旋轉(zhuǎn)矯正方式有:基于圖像邊緣輪廓的旋轉(zhuǎn)矯正和基于傅里葉變換以及霍夫直線檢測(cè)的旋轉(zhuǎn)矯正兩種方法。

1.1 基于圖像邊緣輪廓的旋轉(zhuǎn)矯正

1)圖像灰度化

2)閾值二值化

3)檢測(cè)輪廓

4)提取輪廓的包圍矩陣(圖像前景掩膜)

5)通過(guò)提取的包圍矩陣獲取偏轉(zhuǎn)角度

6)利用仿射變換對(duì)圖像進(jìn)行偏轉(zhuǎn)

具體實(shí)現(xiàn)參考:OpenCV探索之路(十六):圖像矯正技術(shù)深入探討

1.2 基于傅里葉變換以及霍夫直線檢測(cè)的旋轉(zhuǎn)矯正

1)利用DTF變換計(jì)算圖像的頻譜圖

2)頻譜中心移動(dòng)

3)對(duì)移動(dòng)后的頻譜圖進(jìn)行二值分割

4)利用霍夫直線檢測(cè)計(jì)算圖像傾斜角度

5)利用仿射變換對(duì)圖像進(jìn)行旋轉(zhuǎn)矯正

具體實(shí)現(xiàn)參考:OpenCV實(shí)現(xiàn)基于傅里葉變換的旋轉(zhuǎn)文本校正

傅里葉變換的原理解析參考:傅里葉分析之掐死教程(完整版)OpenCV圖像的傅里葉變換-(補(bǔ)番)

2惠赫、基于Hu距圖像旋轉(zhuǎn)矯正

除了以上兩種方式可以實(shí)現(xiàn)圖像旋轉(zhuǎn)矯正外颁褂,還可以利用HU距旋轉(zhuǎn)不變性對(duì)圖像進(jìn)行旋轉(zhuǎn)矯正。

2.1 Hu旋轉(zhuǎn)不變性

圖片經(jīng)過(guò)任意角度旋轉(zhuǎn)检盼、任意比例縮放Hu矩都保持不變肯污,即圖像Hu 矩的平移、旋轉(zhuǎn)不變性。Hu旋轉(zhuǎn)不變性詳情參考:圖像不變性特征—hu矩蹦渣、圖像特征_圖像矩(Hu矩)哄芜。七個(gè)不變矩由二階和三階中心矩的線性組合構(gòu)成,具體表達(dá)式如下:

image

?
在實(shí)際應(yīng)用中,在對(duì)圖片中物體進(jìn)行處理時(shí)柬唯,只有 M1 和 M2 體現(xiàn)良好的不變性认臊,其余的幾個(gè)不變矩產(chǎn)生的誤差比較大。因此锄奢,認(rèn)為只有基于二階矩的不變矩對(duì)二維物體的描述具有良好的旋轉(zhuǎn)失晴、縮放和平移不變性(M1和 M2 由二階矩組成)。

圖像的二階矩表示圖像的主軸拘央,二階矩值的大小可以確定圖像的長(zhǎng)軸和短軸涂屁,以此來(lái)計(jì)算軸的方向角度。最后灰伟,用主軸相對(duì)于法線軸的逆時(shí)針旋轉(zhuǎn)角度來(lái)近似圖像的旋轉(zhuǎn)方向拆又。

2.2 實(shí)現(xiàn)步驟

2.2.1 分別計(jì)算圖像二階距

image

其中,I(x,y)表示圖像在點(diǎn)(x,y)處的灰度值栏账,cx帖族,cy表示圖像的質(zhì)心坐標(biāo)。

2.2.2 利用得到的二階距計(jì)算圖像偏轉(zhuǎn)角度

image

2.2.3 利用仿射變換對(duì)圖像進(jìn)行旋轉(zhuǎn)矯正

2.4 程序?qū)崿F(xiàn)

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

using namespace std;

using namespace cv;


//計(jì)算圖像質(zhì)心坐標(biāo)
bool calCentroid(Mat srcImg, int width, int height, int *x, int *y)
{
    if (srcImg.empty())
    {
        return false;
    }

    int i, j;

    long long m00 = 0, m10 = 0, m01 = 0;

    for (i = 0; i < height; i++)
    {
        for (j = 0; j < width; j++)
        {
            uchar tmp = srcImg.at<uchar>(i, j);
            m00 += tmp;
            m10 += tmp * j;
            m01 += tmp * i;
        }
    }

    if (m00 != 0)
    {
        *x = m10 / m00;
        *y = m01 / m00;
        //cout <<"x:"<<*x<< endl;
        //cout << "y:" << *y << endl;
    }
    else
    {
        *x = 0;
        *y = 0;

        return false;

    }

    return true;

}


//計(jì)算偏轉(zhuǎn)角度
double getAngle(Mat srcImg)
{

    double angle = 0;

    long long  m00 = 0, m11 = 0, m12 = 0, m22 = 0;

    int Rho11 = 0, Rho12 = 0, Rho22 = 0;

    int gx = 0, gy = 0;

    //計(jì)算圖像偏轉(zhuǎn)角度
    if (calCentroid(srcImg, srcImg.cols, srcImg.rows, &gx, &gy) == true)//計(jì)算圖像質(zhì)心
    {
        //cout << "gx:" << gx << endl;
        //cout << "gy:" << gy << endl;

        //歸一化二階中心矩
        for (int i = 0; i < srcImg.rows; i++)
        {
            for (int j = 0; j < srcImg.cols; j++)
            {
                uchar tmp = srcImg.at<uchar>(i, j);
                m00 = m00 + tmp;
                m12 = m12 + (i - gy)*(j - gx)*tmp;
                m11 = m11 + (i - gy)*(i - gy)*tmp;
                m22 = m22 + (j - gx)*(j - gx)*tmp;
            }
        }

        Rho11 = m11 / m00;
        Rho12 = m12 / m00;
        Rho22 = m22 / m00;

    }

    float tempx = 0, tempy = 0;


    //計(jì)算偏轉(zhuǎn)角度
    if (Rho11 > Rho22)
    {
        tempy = Rho11 - Rho22 + sqrt((float)((Rho11 - Rho22)*(Rho11 - Rho22) + 4 * Rho12*Rho12));
        tempx = (float)(-2 * Rho12);

        
        angle = atan(tempy / tempx) * 180 / CV_PI;
        
    }
    else
    {

        tempy = (float)(-2 * Rho12);
        tempx = Rho22 - Rho11 + sqrt((float)((Rho22 - Rho11)*(Rho22 - Rho11) + 4 * Rho12*Rho12));

        angle = atan(tempy / tempx) * 180 / CV_PI;
        
    }


    //偏轉(zhuǎn)角度范圍:-45=< angle <=45
    if (angle >= -45 && angle <= 45)
    {
        angle = angle;
    }
    else if (angle < -45)
    {
        angle = -(90 + angle);
    }
    else if (angle > 45)
    {

        angle = 90 - angle;
    }


    return angle;
}




//對(duì)圖像進(jìn)行旋轉(zhuǎn)矯正
bool ContoursCorrection(Mat srcImg, Mat &dstImage)
{
    
    if (srcImg.empty())
    {
        return false;
    }

    Mat gray, binImg;
    //灰度化
    cvtColor(srcImg, gray, COLOR_RGB2GRAY);
    imshow("灰度圖", gray);
    //二值化
    threshold(gray, binImg, 100, 200, CV_THRESH_BINARY);
    imshow("二值化", binImg);

    vector<vector<Point> > contours;
    vector<Rect> boundRect(contours.size());

    //注意第5個(gè)參數(shù)為CV_RETR_EXTERNAL发笔,只檢索外框  
    findContours(binImg, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE); //找輪廓

    cout << contours.size() << endl;
    for (int i = 0; i < contours.size(); i++)
    {
        //需要獲取的坐標(biāo)  
        CvPoint2D32f rectpoint[4];
        CvBox2D rect = minAreaRect(Mat(contours[i]));

        cvBoxPoints(rect, rectpoint); //獲取4個(gè)頂點(diǎn)坐標(biāo)  
        //與水平線的角度  
        
        

        int line1 = sqrt((rectpoint[1].y - rectpoint[0].y)*(rectpoint[1].y - rectpoint[0].y) + (rectpoint[1].x - rectpoint[0].x)*(rectpoint[1].x - rectpoint[0].x));
        int line2 = sqrt((rectpoint[3].y - rectpoint[0].y)*(rectpoint[3].y - rectpoint[0].y) + (rectpoint[3].x - rectpoint[0].x)*(rectpoint[3].x - rectpoint[0].x));
    
        //面積太小的直接pass
        if (line1 * line2 < 600)
        {
            continue;
        }

        

        //新建一個(gè)感興趣的區(qū)域圖盟萨,大小跟原圖一樣大  
        Mat RoiSrcImg(srcImg.rows, srcImg.cols, CV_8UC3); //注意這里必須選CV_8UC3
        RoiSrcImg.setTo(0); //顏色都設(shè)置為黑色  
        //imshow("新建的ROI", RoiSrcImg);
        //對(duì)得到的輪廓填充一下  
        drawContours(binImg, contours, -1, Scalar(255), CV_FILLED);

        //摳圖到RoiSrcImg
        srcImg.copyTo(RoiSrcImg, binImg);

        ////計(jì)算偏轉(zhuǎn)角度
        double angle = getAngle(binImg);

        cout <<"angle:"<<angle<< endl;


        //為了讓正方形橫著放,所以旋轉(zhuǎn)角度是不一樣的了讨。豎放的捻激,給他加90度,翻過(guò)來(lái)  
        if (line1 > line2)
        {
            angle = 90 + angle;
        }


        //再顯示一下看看前计,除了感興趣的區(qū)域胞谭,其他部分都是黑色的了  
        namedWindow("RoiSrcImg", 1);
        imshow("RoiSrcImg", RoiSrcImg);

        
        //對(duì)RoiSrcImg進(jìn)行旋轉(zhuǎn)  
        Point2f center = rect.center;  //中心點(diǎn) 

        Mat M2 = getRotationMatrix2D(center, angle, 1);//計(jì)算旋轉(zhuǎn)加縮放的變換矩陣 

        warpAffine(RoiSrcImg, dstImage, M2, RoiSrcImg.size(), 1, 0, Scalar(0));//仿射變換 

        imshow("旋轉(zhuǎn)之后", dstImage);
    
    }


    
    return true;
    

}




int main(int argc, char *argv[]) 
{

    //讀取輸入圖像
    Mat input = imread("src.png", IMREAD_COLOR);

    Mat out(input.size(),CV_8UC3,Scalar(0,0,0));

    ContoursCorrection(input, out);

    
    
    waitKey(0);

    system("pause");

    return 0;

}

2.5 旋轉(zhuǎn)矯正效果驗(yàn)證

image

從上圖中可以發(fā)現(xiàn),旋轉(zhuǎn)矯正后圖像由傾斜轉(zhuǎn)換成為水平男杈。


公眾號(hào)《Slater》回復(fù)“機(jī)器學(xué)習(xí)”即可免費(fèi)獲取一份機(jī)器學(xué)習(xí)資料丈屹。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市伶棒,隨后出現(xiàn)的幾起案子旺垒,更是在濱河造成了極大的恐慌,老刑警劉巖肤无,帶你破解...
    沈念sama閱讀 217,185評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件先蒋,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡宛渐,警方通過(guò)查閱死者的電腦和手機(jī)竞漾,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門(mén)眯搭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人业岁,你說(shuō)我怎么就攤上這事鳞仙。” “怎么了笔时?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,524評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵棍好,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我允耿,道長(zhǎng)梳玫,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,339評(píng)論 1 293
  • 正文 為了忘掉前任右犹,我火速辦了婚禮提澎,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘念链。我一直安慰自己盼忌,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評(píng)論 6 391
  • 文/花漫 我一把揭開(kāi)白布掂墓。 她就那樣靜靜地躺著谦纱,像睡著了一般。 火紅的嫁衣襯著肌膚如雪君编。 梳的紋絲不亂的頭發(fā)上跨嘉,一...
    開(kāi)封第一講書(shū)人閱讀 51,287評(píng)論 1 301
  • 那天,我揣著相機(jī)與錄音吃嘿,去河邊找鬼祠乃。 笑死,一個(gè)胖子當(dāng)著我的面吹牛兑燥,可吹牛的內(nèi)容都是我干的亮瓷。 我是一名探鬼主播,決...
    沈念sama閱讀 40,130評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼降瞳,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼嘱支!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起挣饥,我...
    開(kāi)封第一講書(shū)人閱讀 38,985評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤除师,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后扔枫,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體汛聚,經(jīng)...
    沈念sama閱讀 45,420評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評(píng)論 3 334
  • 正文 我和宋清朗相戀三年茧吊,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了贞岭。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,779評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡搓侄,死狀恐怖瞄桨,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情讶踪,我是刑警寧澤芯侥,帶...
    沈念sama閱讀 35,477評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站乳讥,受9級(jí)特大地震影響柱查,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜云石,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評(píng)論 3 328
  • 文/蒙蒙 一唉工、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧汹忠,春花似錦淋硝、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,716評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至铅乡,卻和暖如春继谚,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背阵幸。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,857評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工花履, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人挚赊。 一個(gè)月前我還...
    沈念sama閱讀 47,876評(píng)論 2 370
  • 正文 我出身青樓臭挽,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親咬腕。 傳聞我的和親對(duì)象是個(gè)殘疾皇子欢峰,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評(píng)論 2 354