學(xué)習(xí)OpenCV3:識別圖片傾斜角度并自動旋轉(zhuǎn)


一翘骂、背景

??現(xiàn)有如下圖片驱闷,希望能用鼠標(biāo)畫出矩形,在矩形中計算出圖片的傾斜角度敲董,并由此自動旋轉(zhuǎn)使圖片水平。

二慰安、實(shí)現(xiàn)

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

// 全局變量
Mat g_image_original, g_image_gray, g_image_rect; // 原始圖片腋寨,灰度圖,鼠標(biāo)畫出的矩形圖片
Rect g_rect;                                      // 鼠標(biāo)畫出的矩形的坐標(biāo)
Vec4d g_max_line;                                 // g_rect的最長的線段化焕,用于計算g_angle
double g_angle;                                   // 圖片旋轉(zhuǎn)角度
// 鼠標(biāo)
size_t g_step = 0; // 操作步驟的標(biāo)志
// 進(jìn)度條
int g_Canny_th = 255, g_HoughLinesP_th = 0, g_HoughLinesP_ml = 0, g_HoughLinesP_mg = 0;

// 畫圖函數(shù)
void draw(const string name, const Mat img)
{
    Mat img1 = img.clone();
    if (g_step == 1 || g_step == 2) // 鼠標(biāo)左鍵點(diǎn)擊或按住左鍵移動時畫出矩形
    {
        rectangle(img1, g_rect, Scalar(0, 255, 0), 2);
    }
    if (g_step == 2) // 鼠標(biāo)按住左鍵移動(矩形已畫出)時
    {
        if (g_max_line != Vec4d(0, 0, 0, 0))
        {
            // 畫出識別到的最長的線段
            line(g_image_rect, Point(g_max_line[0], g_max_line[1]), Point(g_max_line[2], g_max_line[3]), Scalar(0, 0, 255), 2);
        }
        // img1(g_rect) = g_image_rect.clone(); // 錯誤:clone會img1(g_rect)重新分配內(nèi)存萄窜,修改img1(g_rect)不會改變img
        g_image_rect.copyTo(img1(g_rect));
    }
    if (g_step == 3) // 奇數(shù)次按下'1'鍵時
    {
        cv::Mat img2 = cv::getRotationMatrix2D(cv::Point2f(img.cols * 0.5f, img.rows * 0.5f), g_angle, 1); // 旋轉(zhuǎn)中心,旋轉(zhuǎn)角度,縮放比例
        warpAffine(img1, img1, img2, img1.size());                                                         // 旋轉(zhuǎn)圖片
    }
    imshow(name, img1);
}

// 滑動條的回調(diào)函數(shù)
void trackbar_callback(int pos, void *)
{
    if (g_step == 2) // 鼠標(biāo)已畫出矩形后查刻,才會去識別矩形中最長的線段
    {
        g_max_line = Vec4d(0, 0, 0, 0);
        Mat img;
        Canny(g_image_gray, img, g_Canny_th, 3 * g_Canny_th); // 邊緣檢測
        //直線檢測键兜,參數(shù):rho,theta穗泵,閾值普气,線段最小長度,共線線段之間的最小間隔
        vector<Vec4d> lines;
        HoughLinesP(img, lines, 1, CV_PI / 180, g_HoughLinesP_th, g_image_gray.cols * g_HoughLinesP_ml / 100, g_image_gray.cols * g_HoughLinesP_mg / 100);
        //獲取最長線段的角度
        double max_lenght = 0; //線段最大長度
        int n = 2;
        for (size_t i = 0; i < lines.size(); i++)
        {
            Vec4i l = lines[i];
            if (l[0] > n && l[0] < img.cols - n && l[1] > n && l[1] < img.rows - n) //去除圖片邊框上的線段
            {
                int length = g_max_line.rows * g_max_line.rows + g_max_line.cols * g_max_line.cols;
                if (max_lenght < length) // 尋找最長線段
                {
                    max_lenght = length;
                    g_max_line = l;
                }
            }
        }
        //計算傾斜角度
        if (g_max_line[0] != g_max_line[2])
            g_angle = atan((g_max_line[3] - g_max_line[1]) / (g_max_line[2] - g_max_line[0])) * 180 / CV_PI;
        else
            g_angle = 90;
        cvtColor(img, g_image_rect, COLOR_GRAY2BGR); // 灰度圖轉(zhuǎn)彩圖佃延,方便畫彩線
    }
}

// 鼠標(biāo)事件的回調(diào)函數(shù)
void mouse_callback(int event, int x, int y, int flags, void *param)
{
    switch (event)
    {
    case EVENT_LBUTTONDOWN: // 鼠標(biāo)左鍵點(diǎn)擊
        if (g_step == 0)
        {
            g_step = 1;
            g_rect.x = x;
            g_rect.y = y;
        }
        break;
    case EVENT_MOUSEMOVE: // 鼠標(biāo)移動
        if (g_step == 1)
        {
            g_rect.width = x - g_rect.x;
            g_rect.height = y - g_rect.y;
        }
        break;
    case EVENT_LBUTTONUP: // 鼠標(biāo)左鍵釋放
        g_step = 2;
        g_image_rect = g_image_original(g_rect).clone();
        cvtColor(g_image_rect, g_image_gray, COLOR_BGR2GRAY); // 灰度圖
        break;
    case EVENT_RBUTTONDOWN: // 鼠標(biāo)右鍵點(diǎn)擊现诀,清除內(nèi)容
        g_step = 0;
        g_rect = Rect(0, 0, 0, 0);
        g_max_line = Vec4d(0, 0, 0, 0);
        break;
    default:
        break;
    }
}

int main()
{
    const string name = "image";
    namedWindow(name, WINDOW_AUTOSIZE);
    g_image_original = imread("E:/VSCode/git/my_program/image/1.PNG");
    if (g_image_original.empty())
    {
        cout << "can not open image!" << endl;
        return -1;
    }
    setMouseCallback(name, mouse_callback);                                  // 鼠標(biāo)
    createTrackbar("threshold1", name, &g_Canny_th, 255, trackbar_callback); // 進(jìn)度條
    createTrackbar("threshold2", name, &g_HoughLinesP_th, 255, trackbar_callback);
    createTrackbar("min_length", name, &g_HoughLinesP_ml, 100, trackbar_callback);
    createTrackbar("   max_gap", name, &g_HoughLinesP_mg, 100, trackbar_callback);
    while (true)
    {
        trackbar_callback(0, 0);      // 進(jìn)度條函數(shù)
        draw(name, g_image_original); // 畫圖
        char c = waitKey(10);
        if (c == '1') // 按'1'第奇數(shù)次,旋轉(zhuǎn)圖片履肃;按'1'第偶數(shù)次仔沿,還原圖片
        {
            if (g_step == 2) // 旋轉(zhuǎn)圖片的命令標(biāo)志
                g_step = 3;
            else if (g_step == 3) // 還原圖片的命令標(biāo)志
                g_step = 2;
        }
        else if (c > 0 && c != '1') // 退出循環(huán)
            break;
    }
    destroyAllWindows();
    return 0;
}

滑動條參數(shù)

// 邊緣檢測
Canny(g_image_gray, img, g_Canny_th, 3 * g_Canny_th); 
// 直線檢測
HoughLinesP(img, lines, 1, CV_PI / 180, g_HoughLinesP_th, g_image_gray.cols * g_HoughLinesP_ml / 100, g_image_gray.cols * g_HoughLinesP_mg / 100);

threshold1
??對應(yīng)g_Canny_th,是檢測物體邊緣的閾值尺棋。
threshold2
??對應(yīng)g_HoughLinesP_th封锉,是提取直線的閾值。
min_length
??對應(yīng)g_HoughLinesP_ml膘螟,由于鼠標(biāo)畫出的矩形成福,其寬度不固定,故使用百分比萍鲸。只識別長度大于g_image_gray.cols * g_HoughLinesP_ml / 100的直線闷叉。
max_gap
??對應(yīng)g_HoughLinesP_mg,也是使用百分比脊阴。若兩條小線段在同一條直線上握侧,且其間隔小于g_image_gray.cols * g_HoughLinesP_mg / 100,則將其連接成一條直線嘿期,否則將其看作兩條直線品擎。

操作方法
??運(yùn)行程序,在空白圖片上點(diǎn)擊鼠標(biāo)左鍵并按住移動备徐,由此畫出矩形萄传。設(shè)置窗口中的滑動條參數(shù)使矩形中出現(xiàn)最長的紅線,由此自動計算出傾斜角度蜜猾。在鍵盤按下1鍵可旋轉(zhuǎn)圖片使其水平秀菱,再按1鍵一次,可使其復(fù)原蹭睡。點(diǎn)擊鼠標(biāo)右鍵能夠擦除已畫好的矩形衍菱,由此可以重新作畫。按鍵盤除1以外的鍵可自動退出程序肩豁。

運(yùn)行結(jié)果

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末脊串,一起剝皮案震驚了整個濱河市辫呻,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌琼锋,老刑警劉巖放闺,帶你破解...
    沈念sama閱讀 217,542評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異缕坎,居然都是意外死亡怖侦,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評論 3 394
  • 文/潘曉璐 我一進(jìn)店門念赶,熙熙樓的掌柜王于貴愁眉苦臉地迎上來础钠,“玉大人,你說我怎么就攤上這事叉谜∑煊酰” “怎么了?”我有些...
    開封第一講書人閱讀 163,912評論 0 354
  • 文/不壞的土叔 我叫張陵停局,是天一觀的道長很钓。 經(jīng)常有香客問我,道長董栽,這世上最難降的妖魔是什么码倦? 我笑而不...
    開封第一講書人閱讀 58,449評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮锭碳,結(jié)果婚禮上袁稽,老公的妹妹穿的比我還像新娘。我一直安慰自己擒抛,他們只是感情好推汽,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,500評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著歧沪,像睡著了一般歹撒。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上诊胞,一...
    開封第一講書人閱讀 51,370評論 1 302
  • 那天暖夭,我揣著相機(jī)與錄音,去河邊找鬼撵孤。 笑死迈着,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的邪码。 我是一名探鬼主播寥假,決...
    沈念sama閱讀 40,193評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼霞扬!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,074評論 0 276
  • 序言:老撾萬榮一對情侶失蹤喻圃,失蹤者是張志新(化名)和其女友劉穎萤彩,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體斧拍,經(jīng)...
    沈念sama閱讀 45,505評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡雀扶,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,722評論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了肆汹。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片愚墓。...
    茶點(diǎn)故事閱讀 39,841評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖昂勉,靈堂內(nèi)的尸體忽然破棺而出浪册,到底是詐尸還是另有隱情,我是刑警寧澤岗照,帶...
    沈念sama閱讀 35,569評論 5 345
  • 正文 年R本政府宣布村象,位于F島的核電站,受9級特大地震影響攒至,放射性物質(zhì)發(fā)生泄漏厚者。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,168評論 3 328
  • 文/蒙蒙 一迫吐、第九天 我趴在偏房一處隱蔽的房頂上張望库菲。 院中可真熱鬧,春花似錦志膀、人聲如沸熙宇。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽奇颠。三九已至,卻和暖如春放航,著一層夾襖步出監(jiān)牢的瞬間烈拒,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評論 1 269
  • 我被黑心中介騙來泰國打工广鳍, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留荆几,地道東北人。 一個月前我還...
    沈念sama閱讀 47,962評論 2 370
  • 正文 我出身青樓赊时,卻偏偏與公主長得像吨铸,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子祖秒,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,781評論 2 354