openCV文檔圖像校正之巧用cvMinAreaRect2()函數

openCV文檔圖像校正之巧用cvMinAreaRect2()函數

  • 問題
  • 解決方案
  • 原理探究:什麼是cvMinAreaRect2()函數
  • 總結

閱讀之前注意:

本文閱讀建議用時:45min
本文閱讀結構如下表:

項目 下屬項目 測試用例數量
問題 0
解決方案 1
原理探究:什麼是cvMinAreaRect2()函數 1
總結 0

問題

對於傾斜的文檔圖像,我們首先需要找出傾斜角,之後旋轉校正即可。
比如把下圖
原圖
校正爲下圖
這裏寫圖片描述

解決方案

之前我有寫過系列文檔圖像傾斜校正的Matlab程序,思路可以借鑑。但是現在我提出來的解決方法是另外一種思路:當我們把文字經過閉運算、膨脹、腐蝕等處理後得到連成大概像一個矩形的長條。類似下圖:
這裏寫圖片描述
那麼我們可以找到最長的長條。之後如果我們能夠計算出長條的外接矩形的話,外接矩形的傾斜角即是圖像的傾斜角!事實上,openCV給我們提供了非常方便的函數來尋找這樣的矩形,使用cvMinAreaRect2()函數即可得到這個長條的外接矩形,而外接矩形的傾斜角能夠直接查詢到。
以下是具體代碼(注意看註釋):

//主要應用了cvMinAreaRect2()函數 得到線的傾斜角
#include <iostream>  
#include <opencv2/core/core.hpp>  
#include <opencv2/highgui/highgui.hpp>  
#include <opencv2/opencv.hpp>//如果要包含所有庫

using namespace cv;

//圖片旋轉操作 
void imrotate(Mat& src, Mat& dst, double angle)
{
    cv::Point2f center((float)src.cols / 2, (float)src.rows / 2);
    cv::Mat rot = cv::getRotationMatrix2D(center, angle, 1);
    cv::Rect bbox = cv::RotatedRect(center, src.size(), angle).boundingRect();

    rot.at<double>(0, 2) += bbox.width / 2.0 - center.x;
    rot.at<double>(1, 2) += bbox.height / 2.0 - center.y;
    cv::warpAffine(src, dst, rot, bbox.size());
}

void main()
{
    int i = 0, j = 0;
    Mat img = imread("26.bmp", 0);//讀取圖像img。0表示轉換爲灰度圖像讀入
    Mat saveImg = imread("26.bmp", 0);
    //imshow("savwImg", saveImg);
    if (img.empty())
    {
        printf("當前文件目錄下沒有那張圖片\n");
        system("pause"); exit(0);
    }
    int row = img.rows;
    int col = img.cols;
    imshow("原圖", img);

    //圖像預處理部分--------------------------------------------------------
    threshold(img, img, 0, 255, CV_THRESH_OTSU);//最大類間方差法進行二值化處理
    img = ~img;//把img取反,變爲黑色背景,白色字,便於形態學處理

    morphologyEx(img, img, MORPH_CLOSE, Mat(18, 18, CV_8U), Point(-1, -1), 1);//形態學閉操作
    dilate(img, img, Mat(12, 12, CV_8U), Point(-1, -1), 1);//膨脹
    erode(img, img, Mat(18, 18, CV_8U), Point(-1, -1), 1);//腐蝕

    Size sz;
    pyrDown(img, img, sz, BORDER_DEFAULT);//縮小圖片
    pyrDown(img, img, sz, BORDER_DEFAULT);//縮小圖片
    imwrite("27.png", img);

    //獲取傾斜角------------------------------------------------------
    IplImage imgTmp = img;//爲了方便程序運行,這裏採用老版的圖像數據類型
    IplImage  *src = cvCloneImage(&imgTmp);//圖像數據的深拷貝
    cvShowImage("預處理後的圖像", src);

    CvMemStorage* storage = cvCreateMemStorage(0);
    CvSeq* contour = NULL;//輪廓
    CvSeq* maxLenContour = NULL;//最長長條的輪廓
    int count = cvFindContours(src, storage, &contour, \
        sizeof(CvContour), CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE);//找出各個連通域
    CvBox2D box;//外接矩形
    CvBox2D maxLenBox;//最長長條的外接矩形
    float boxLen = 0.;//矩形的長或者寬
    for (; contour != 0; contour = contour->h_next)//找到最長的長條
    {
        box = cvMinAreaRect2(contour, 0);//調用cvMinAreaRect2()函數返回外接矩形給box
        if (boxLen < box.size.width)
        {
            boxLen = box.size.width;
            maxLenContour = contour;
            maxLenBox = box;
        }
        if (boxLen < box.size.height)
        {
            boxLen = box.size.height;
            maxLenContour = contour;
            maxLenBox = box;
        }
    }
    printf("maxBoxWidth is %f\n", maxLenBox.size.width);
    printf("maxBoxHeight is %f\n", maxLenBox.size.height);
    printf("angle is %f\n", maxLenBox.angle);

    //旋轉校正-------------------------------------------------------
    Mat newImg;

    if (maxLenBox.size.width >= maxLenBox.size.height)
    {
        imrotate(saveImg, newImg, maxLenBox.angle);
        printf("rotate angle is %f\n", maxLenBox.angle);
    }
    else
    {
        imrotate(saveImg, newImg, 90 + maxLenBox.angle);
        printf("rotate angle is %f\n", 90 + maxLenBox.angle);
    }
    pyrDown(newImg, newImg, sz, BORDER_DEFAULT);//縮小圖片
    pyrDown(newImg, newImg, sz, BORDER_DEFAULT);//縮小圖片
    namedWindow("校正圖");
    imshow("校正圖", newImg);
    waitKey(100000);//等待100秒後窗口關閉
}

原理探究:什麼是cvMinAreaRect2()函數

以下借鑑說明:函數cvMinAreaRect2()可以返回一個包圍輪廓最小的長方形,這個長方形可以是傾斜的;請看下圖8-7中的坦克,該函數的參數和cvBoundingRect()的相似。opencv的數據類型CvBox2D就是用來表述這樣的長方形狀的。
這裏寫圖片描述

但是上圖還有一定的錯誤,我們知道:在openCV中的默認座標系是這樣的,左上角是座標原點,從原點往右是X軸正半軸,從原點往下是Y軸正半軸。而函數cvMinAreaRect2()返回的數據類型CvBox2D,其定義中的寬是圖片順時針轉先碰到X軸的那條邊,因此寬可能比長要大,而定義中的角度則是寬與X軸的夾角,因此角度永遠是負值。
按照這樣的規則,圖8-7就是錯誤的,除了可以形象表示“長方形可以是傾斜的”就沒有對的了。。。
我們知道,一張圖片,要麼是左傾斜(大於或小於45度),要麼是右傾斜(大於或小於45度),因此我們測試以下的四副圖片
這裏寫圖片描述這裏寫圖片描述這裏寫圖片描述這裏寫圖片描述
得到的角度分別是-77.9度、-13.8度、-13.7度、-58.7度。

具體參考以下代碼(測試圖片可以從這裏獲取):

#include <iostream>  
#include <opencv2/core/core.hpp>  
#include <opencv2/highgui/highgui.hpp>  
#include <opencv2/opencv.hpp>//如果要包含所有庫

using namespace cv;

//圖片旋轉操作 
void imrotate(Mat& src, Mat& dst, double angle)
{
    cv::Point2f center((float)src.cols / 2, (float)src.rows / 2);
    cv::Mat rot = cv::getRotationMatrix2D(center, angle, 1);
    cv::Rect bbox = cv::RotatedRect(center, src.size(), angle).boundingRect();

    rot.at<double>(0, 2) += bbox.width / 2.0 - center.x;
    rot.at<double>(1, 2) += bbox.height / 2.0 - center.y;
    cv::warpAffine(src, dst, rot, bbox.size());
}

void main()
{
    Mat img = imread("rhh.bmp", 0);
    Mat saveImg = imread("rhh.bmp", 0);
    if (img.empty())
    {
        printf("當前文件目錄下沒有那張圖片\n");
        system("pause"); exit(0);
    }
    threshold(img, img, 0, 255, CV_THRESH_OTSU);//最大類間方差法
    imshow("原圖", img);

    img = ~img;

    IplImage imgTmp = img;
    IplImage  *src = cvCloneImage(&imgTmp);
    //cvShowImage("Source", src);
    CvMemStorage* storage = cvCreateMemStorage(0);
    CvSeq* contour = NULL;//輪廓
    CvSeq* maxLenContour = NULL;//最長長條的輪廓
    int count = cvFindContours(src, storage, &contour, \
        sizeof(CvContour), CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE);//找出各個連通域
    CvBox2D box;//外接矩形
    CvBox2D maxLenBox;//最長長條的外接矩形
    float boxLen = 0.;//矩形的長或者寬
    for (; contour != 0; contour = contour->h_next)//找到最長的長條
    {
        box = cvMinAreaRect2(contour, 0);//調用cvMinAreaRect2()函數返回外接矩形給box
        if (boxLen < box.size.width)
        {
            boxLen = box.size.width;
            maxLenContour = contour;
            maxLenBox = box;
        }
        if (boxLen < box.size.height)
        {
            boxLen = box.size.height;
            maxLenContour = contour;
            maxLenBox = box;
        }
    }
    printf("maxBoxWidth is %f\n", maxLenBox.size.width);
    printf("maxBoxHeight is %f\n", maxLenBox.size.height);
    printf("angle is %f\n", maxLenBox.angle);

    Mat newImg;
    if (maxLenBox.size.width >= maxLenBox.size.height)
    {
        imrotate(saveImg, newImg, maxLenBox.angle);
        printf("rotate angle is %f\n", maxLenBox.angle);
    }   
    else
    {
        imrotate(saveImg, newImg, 90 + maxLenBox.angle);
        printf("rotate angle is %f\n", 90 + maxLenBox.angle);
    }   
    namedWindow("校正圖");
    imshow("校正圖", newImg);
    waitKey(100000);
}

總結

本篇博客的核心是:弄清楚cvMinAreaRect2()函數返回的數據類型CvBox2D中的參數的定義。

友情鏈接1:cvBox2D和RotatedRect中返回的角度angle詳解
友情鏈接2:opencv輪廓及點在輪廓內判斷

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章