學習OpenCV有一段時間了,雖然已經正式成爲拉環小哥整整一個月,可是仍然想着學習本身喜歡的。說實話,想着你們都在往本身喜歡的方向奔,我不肯成爲無所事事的人。函數
可能仍是MSP的氛圍好吧,想起昕羽姐辭掉微軟的工做,一心想當一名插畫師,去年這個時候還寫明信片鼓勵我。你們都這麼優秀,說真的,不想拖你們後腿。學習
車輛段是挺好的一個地方,確實沒想到會分回來。既然來了那就先這樣唄,最起碼我要先把本身的生活安頓下來,最起碼要讓生活走上正軌。測試
如今的我沒有什麼突出的能力,沒有什麼錢,沒有什麼過硬的技術。這些都只能慢慢來,說到底,仍是我本身太菜了。spa
好了,不說這些有的沒的了。.net
如今拿工資了,終於有錢買實驗設備跟學習教程了!很開心!code
言歸正傳,說說這兩天學的Open CV切邊。orm
七月份剛畢業的時候,在淘寶上花了15塊買了份Open CV的盜版視頻。視頻
下載下來發現是51CTO的收費視頻,想着這麼貴講的應該還不錯,而後就學習了下。blog
這個老師叫賈志剛,我喜歡叫他沙雕老師,由於,講的實在是太沙雕了。在第一部分的課裏,淨在講騷話。從高數到語文,從歷史到政治,不只教你背古詩還教你撩妹!說騷話張口就來,一講到硬核的部分就emmm...真是服氣。聽不懂也沒辦法只能在網上搜一下相關的文章,看看原理,補一補高數。教程
抄抄老師的代碼,運行一下,總結一下,就這樣混過去了。
直到這兩天,開始作小案例。
小案例就是把歪着的圖片矯正,而且切除多餘部分。
看,就是下面這個dogB 王源
而後要矯正。
這個沙雕老師真是坑的一匹,代碼寫的只有他的圖片能用,原理也講不通,水的要死。
原理是這樣的:
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...
矯正完了就成這樣了。
值得注意的是,我並非按照上面這樣寫的。個人代碼加了中值濾波,爲了消除噪聲。還有很頭疼的陰影。
edianBlur(src, mBlur,11);
(11的模糊力度特別大,邊緣特別容易識別。)
接下來就是切去周圍多餘的邊了,然而坑爹的就在這兒了。
矯正以後反而還識別不了了。What The Fuck?你原圖都識別出來了,矯正還識別不出來了?
我仔細找了下緣由:
矯正後,
if (max_width == minRect.size.width && max_height == minRect.size.height)
該條件沒法成立,
按道理來講咱們應該選綠框,那怎麼才能挑出綠框呢?
那就記錄找出知足任意max條件的面積最大的,或者你你看max_Width不能小於多少,把紅框過濾掉。
這樣就解決了最頭疼的問題。
大功告成~
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