【TFBoys路轉黑】OpenCV切邊

寫在前面的那些有的沒的

學習OpenCV有一段時間了,雖然已經正式成爲拉環小哥整整一個月,可是仍然想着學習本身喜歡的。說實話,想着你們都在往本身喜歡的方向奔,我不肯成爲無所事事的人。函數

可能仍是MSP的氛圍好吧,想起昕羽姐辭掉微軟的工做,一心想當一名插畫師,去年這個時候還寫明信片鼓勵我。你們都這麼優秀,說真的,不想拖你們後腿。學習

車輛段是挺好的一個地方,確實沒想到會分回來。既然來了那就先這樣唄,最起碼我要先把本身的生活安頓下來,最起碼要讓生活走上正軌。測試

如今的我沒有什麼突出的能力,沒有什麼錢,沒有什麼過硬的技術。這些都只能慢慢來,說到底,仍是我本身太菜了。spa

好了,不說這些有的沒的了。.net

如今拿工資了,終於有錢買實驗設備跟學習教程了!很開心!code

言歸正傳,說說這兩天學的Open CV切邊。orm

什麼是OpenCV切邊?

七月份剛畢業的時候,在淘寶上花了15塊買了份Open CV的盜版視頻。視頻

clipboard.png

下載下來發現是51CTO的收費視頻,想着這麼貴講的應該還不錯,而後就學習了下。blog

這個老師叫賈志剛,我喜歡叫他沙雕老師,由於,講的實在是太沙雕了。在第一部分的課裏,淨在講騷話。從高數到語文,從歷史到政治,不只教你背古詩還教你撩妹!說騷話張口就來,一講到硬核的部分就emmm...真是服氣。聽不懂也沒辦法只能在網上搜一下相關的文章,看看原理,補一補高數。教程

抄抄老師的代碼,運行一下,總結一下,就這樣混過去了。

直到這兩天,開始作小案例。

小案例就是把歪着的圖片矯正,而且切除多餘部分。
看,就是下面這個dogB 王源

clipboard.png

而後要矯正。
這個沙雕老師真是坑的一匹,代碼寫的只有他的圖片能用,原理也講不通,水的要死。

clipboard.png

原理是這樣的:

Canny檢測邊緣->比較最大的輪廓->肯定最大輪廓->按照輪廓畫圖->矯正切邊

從代碼能夠看出來:

代碼我有至關一部分看的是這篇文章:
https://blog.csdn.net/weixin_...
int main(int,void*)
{
//...imread..xxxx
Check_Skew();  
}

void Check_Skew(); 
{
    Mat canny_output;          
    cvtColor(src_img, gray_img, COLOR_BGR2GRAY);         //將原圖轉化爲灰度圖
    Canny(gray_img, canny_output, threshold_value, threshold_value * 2, 3, false);      // canny邊緣檢測
 
    vector<vector<Point>> contours;
    vector<Vec4i> hireachy;
    findContours(canny_output, contours, hireachy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0));    // 找到全部輪廓
    Mat drawImg = Mat::zeros(src_img.size(), CV_8UC3);   
    float max_width = 0;       // 定義最大寬度
    float max_height = 0;      // 定義最大高度
    double degree = 0;         // 定義旋轉角度
    for (auto t = 0; t < contours.size(); ++t)            // 遍歷每個輪廓   
    { 
        RotatedRect minRect = minAreaRect(contours[t]);        // 找到每個輪廓的最小外包旋轉矩形,RotatedRect裏面包含了中心座標、尺寸以及旋轉角度等信息   
        degree = abs(minRect.angle);         
        if (degree > 0) 
        {
            max_width = max(max_width, minRect.size.width);      
            max_height = max(max_height, minRect.size.height);
        }
    }
    RNG rng(12345);        
    for (auto t = 0; t < contours.size();++t) 
    {
        RotatedRect minRect = minAreaRect(contours[t]);    
        if (max_width == minRect.size.width && max_height == minRect.size.height)
        {
            degree = minRect.angle;   // 保存目標輪廓的角度
            Point2f pts[4];
            minRect.points(pts);
            Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));  //產生隨機顏色
            for (int i = 0; i < 4; ++i)
            {
                line(drawImg, pts[i], pts[(i + 1) % 4], color, 2, 8, 0);
            }
        }
    }
    
    imshow("找到的矩形輪廓", drawImg);
    Point2f center(src_img.cols / 2, src_img.rows / 2);
    Mat rotm = getRotationMatrix2D(center, degree, 1.0);    //獲取仿射變換矩陣
    Mat dst;
    warpAffine(src_img, dst, rotm, src_img.size(), INTER_LINEAR, 0, Scalar(255, 255, 255));    // 進行圖像旋轉操做
    imwrite("123.png", dst);      //將校訂後的圖像保存下來
    imshow("Correct Image", dst);

}

這些代碼看起來比較長,我分幾部分。

這些是找輪廓的:

Mat canny_output;          
    cvtColor(src_img, gray_img, COLOR_BGR2GRAY);         //將原圖轉化爲灰度圖
    Canny(gray_img, canny_output, threshold_value, threshold_value * 2, 3, false);      // canny邊緣檢測
 
    vector<vector<Point>> contours;
    vector<Vec4i> hireachy;
    findContours(canny_output, contours, hireachy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0));    // 找到全部輪廓

這些是找最大的輪廓並記錄它的高度寬度,以用於後面繪製它

Mat drawImg = Mat::zeros(src_img.size(), CV_8UC3);   
    float max_width = 0;       // 定義最大寬度
    float max_height = 0;      // 定義最大高度
    double degree = 0;         // 定義旋轉角度
    for (auto t = 0; t < contours.size(); ++t)            // 遍歷每個輪廓   
    { 
        RotatedRect minRect = minAreaRect(contours[t]);        // 找到每個輪廓的最小外包旋轉矩形,RotatedRect裏面包含了中心座標、尺寸以及旋轉角度等信息   
        degree = abs(minRect.angle);         
        if (degree > 0) 
        {
            max_width = max(max_width, minRect.size.width);      
            max_height = max(max_height, minRect.size.height);
        }
    }

這些是挨個比較,看大家這一堆輪廓中哪一個是我剛剛找到的最大輪廓,而且繪製出來。
(這地方就是沙雕老師水平不行的地方,代碼寫得稀爛,明明能一次作完的事兒非要幹第二次,有點蠢)

RNG rng(12345);        
    for (auto t = 0; t < contours.size();++t) 
    {
        RotatedRect minRect = minAreaRect(contours[t]);    
        if (max_width == minRect.size.width && max_height == minRect.size.height)
        {
            degree = minRect.angle;   // 保存目標輪廓的角度
            Point2f pts[4];
            minRect.points(pts);
            Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));  //產生隨機顏色
            for (int i = 0; i < 4; ++i)
            {
                line(drawImg, pts[i], pts[(i + 1) % 4], color, 2, 8, 0);
            }
        }
    }

剩下的只有一行比較關鍵:

warpAffine(src_img, dst, rotm, src_img.size(), INTER_LINEAR, 0, Scalar(255, 255, 255)); // 進行圖像旋轉操做

這是旋轉圖像的API,關鍵在於旋轉以後,你要按着這個沙雕老師抄就會發現圖片的空餘地方是白色的。你須要修改的是INTER_LINEAR後面邊界填充參數,就是問你想用什麼東西填充空白。沙雕老師寫了個0,我這是BORDER_REPLICATE看我的狀況吧,不知道咋用能夠看看這個

https://blog.csdn.net/qq_2494...

clipboard.png

矯正完了就成這樣了。

值得注意的是,我並非按照上面這樣寫的。個人代碼加了中值濾波,爲了消除噪聲。還有很頭疼的陰影。

edianBlur(src, mBlur,11);

(11的模糊力度特別大,邊緣特別容易識別。)

矯正後

接下來就是切去周圍多餘的邊了,然而坑爹的就在這兒了。

矯正以後反而還識別不了了。What The Fuck?你原圖都識別出來了,矯正還識別不出來了?

我仔細找了下緣由:

矯正後,

if (max_width == minRect.size.width && max_height == minRect.size.height)

該條件沒法成立,

clipboard.png

按道理來講咱們應該選綠框,那怎麼才能挑出綠框呢?

那就記錄找出知足任意max條件的面積最大的,或者你你看max_Width不能小於多少,把紅框過濾掉。

這樣就解決了最頭疼的問題。

clipboard.png

大功告成~

PS:期間找不到邊緣的時候我曾懷疑過是否是我邊緣不夠亮,還用PS修了一下。。媽個雞想一想都以爲丟人。

最後,切邊總算是快要結束了~

附切邊代碼:

void findROI(int, void*)
{
    
    printf("**************當前閾值:%d******************************\n", threshold_value);
    //cvtColor(src_img, gray_img, COLOR_BGR2GRAY);      //將原圖轉化爲灰度圖
    Mat canny_output;
    Mat mBlur;
    medianBlur(src, mBlur, 11);
    Canny(mBlur, canny_output, threshold_value, threshold_value * 2, 3, false);                // canny邊緣檢測
    imshow("canny_output", canny_output);
    vector<vector<Point>> contours;
    vector<Vec4i> hireachy;
    findContours(canny_output, contours, hireachy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0));    // 調用API,找到輪廓

    // 篩選contours中的輪廓,咱們須要最大的那個輪廓
    float max_width = 0;       // 定義最大寬度
    float max_height = 0;      // 定義最大高度
    double degree = 0;         // 定義旋轉角度
    //這個for是爲了找最大的框
    for (auto t = 0; t < contours.size(); ++t)            // 遍歷每個輪廓   
    {
        RotatedRect minRect = minAreaRect(contours[t]);        // 找到每個輪廓的最小外包旋轉矩形,RotatedRect裏面包含了中心座標、尺寸以及旋轉角度等信息   
        degree = abs(minRect.angle);
        max_width = max(max_width, minRect.size.width);
        max_height = max(max_height, minRect.size.height);
    }

    RNG rng(12345);                            //定義一個隨機數產生器,用來產生不一樣顏色的矩形框
    Mat drawImage = Mat::zeros(src_img.size(), CV_8UC3);
    Rect bbox;
    for (auto t = 0; t < contours.size(); ++t)        // 遍歷每個輪廓
    {
        RotatedRect minRect = minAreaRect(contours[t]);        // 找到每個輪廓的最小外包旋轉矩形,RotatedRect裏面包含了中心座標、尺寸以及旋轉角度等信息
        
        if ((minRect.size.width == max_width || minRect.size.height == max_height) && minRect.size.width > 620)   //篩選最小外包旋轉矩形
        {
            printf("current angle : %f\n", degree);
            Mat vertices;       // 定義一個4行2列的單通道float類型的Mat,用來存儲旋轉矩形的四個頂點
            boxPoints(minRect, vertices);    // 計算旋轉矩形的四個頂點座標
            bbox = boundingRect(vertices);   //找到輸入點集的最小外包直立矩形,返回Rect類型
            cout << "最小外包矩形:" << bbox << endl;
            Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));   //產生隨機顏色
            for (int i = 0; i < 4; i++)             // 遍歷每一個旋轉矩形的四個頂點座標
            {
                // 在相鄰的頂點之間繪製直線
                Mat p1 = vertices.row(i); // 使用成員函數row(i)和col(j)獲得矩陣的第i行或者第j列,返回值仍然是一個單通道的Mat類型
                int j = (i + 1) % 4;
                Mat p2 = vertices.row(j);
                Point p1_point = Point(p1.at<float>(0, 0), p1.at<float>(0, 1)); //將Mat類型的頂點座標轉換爲Point類型
                Point p2_point = Point(p2.at<float>(0, 0), p2.at<float>(0, 1));
                line(src, p1_point, p2_point, color, 2, 8, 0);    // 根據獲得的四個頂點,經過鏈接四個頂點,將最小旋轉矩形繪製出來
            }
        }
    }
    imshow(output_win, src);

    if (bbox.width > 0 && bbox.height > 0)
    {
        Mat roiImg = src_img(bbox);        //從原圖中截取興趣區域
        namedWindow(roi_win, CV_WINDOW_AUTOSIZE);
        imshow(roi_win, roiImg);
    }

    return;
}

下個小案例見!

有啥不懂的能夠問我~也能夠單獨分模塊測試。Sangyu.Li@outlook.com

相關文章
相關標籤/搜索