您好,登錄后才能下訂單哦!
這篇文章主要介紹了怎么用C++ OpenCV實現文檔矯正功能的相關知識,內容詳細易懂,操作簡單快捷,具有一定借鑒價值,相信大家閱讀完這篇怎么用C++ OpenCV實現文檔矯正功能文章都會有所收獲,下面我們一起來看看吧。
將一個斜著拍攝的文檔矯正成正的,如圖所示:
1.讀取原始圖像,若圖像太大可以先進行縮放處理,并獲取原始圖像的寬和高
2.對圖像進行預處理得到邊緣,依次進行灰度處理、高斯模糊、邊緣檢測、膨脹、腐蝕。
3.找到最大的輪廓,并提取角點
進行降噪處理:檢測輪廓面積,只保留大于閾值面積的輪廓
計算每個輪廓的周長,使用DP算法計算出輪廓點的個數,規則為周長*0.02
找到圖像中面積最大的,且角點為4的輪廓
4.將找到的四個角點排列成一個固定的順序,排列后的順序為:左上角-右上角-左下角-右下角
將每個點的xy坐標值相加(x+y),左上角的點的坐標和應該是最小的,右下角的點的坐標和應該是最大的
將每個點的xy坐標值相減(x-y),左下角的點的坐標差應該是最小的,右上角的點的坐標差應該是最大的
重新排列四個角點
5.進行透視變換
根據變換前及變換后的四個角點,創建變換矩陣
根據變換矩陣對圖像進行透視變換
6.若透視變換后有一些毛邊,按需要進行裁剪,裁剪后重新調整比例
創建一個矩形用來裁剪,并設定四周裁剪5像素
裁剪后重新調整圖像寬高
7.顯示變換后圖像
代碼中均有詳細注釋,請仔細閱讀
#include <iostream> #include<opencv2/opencv.hpp> #include <opencv2/highgui.hpp> #include <opencv2/imgproc.hpp> using namespace cv; using namespace std; // 一些定義 Mat image_origin, // 原始圖像 image_gray, // 灰度處理后的圖像 image_blur, // 高斯模糊處理后的圖像 image_canny, // 邊緣檢測后的圖像 image_dilate, // 膨脹后的圖像 image_erode, // 腐蝕后的圖像 image_preprocess, // 預處理后的圖像 image_trans, // 透視變換后的圖像 image_crop; // 裁剪后的圖像 vector<Point> origin_points, // 重新排列前的角點 reorder_points; // 重新排列后的角點 int origin_width = 0, origin_height = 0; /* * 函數功能:預處理,依次進行灰度處理、高斯模糊、邊緣檢測、膨脹、腐蝕。 * 輸入:圖像,是否顯示(0-不顯示 1-顯示每一步處理后的圖像 2-只顯示最終圖像) * */ Mat PreProcess(const Mat& image, int display) { // 灰度處理 cvtColor(image, image_gray, COLOR_BGR2GRAY); // 高斯模糊 GaussianBlur(image_gray, image_blur, Size(3, 3), 3, 0); // 邊緣檢測(邊緣檢測前對圖像進行一次高斯模糊) Canny(image_blur, image_canny, 50, 150); // 膨脹和腐蝕(有時進行邊緣檢測的時候,沒有被完全填充,或者無法正確檢測,可以用膨脹和腐蝕) // 創建一個用于膨脹和腐蝕的內核,后面的數字越大膨脹的越多(數字要用奇數) Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3)); // 膨脹 dilate(image_canny, image_dilate, kernel); // 腐蝕 //erode(image_dilate, image_erode, kernel); // 顯示預處理效果 if(display == 1) { imshow("灰度處理后的圖像", image_gray); imshow("高斯模糊后的圖像", image_blur); imshow("邊緣檢測后的圖像", image_canny); imshow("膨脹后的圖像", image_dilate); // imshow("腐蝕后的圖像", image_erode); } else if(display == 2) { imshow("預處理后的圖像", image_dilate); } return image_dilate; } /* * 函數功能:找到面積最大的輪廓 * 輸入:源圖像 * 輸出:最大輪廓的四個角點數組 * */ vector<Point> GetMaxContour(const Mat& img_input) { /* * contours是一個雙重向量,向量內每個元素保存了一組由連續的Point點構成的點的集合的向量,每一組Point點集就是一個輪廓。有多少輪廓,向量contours就有多少元素。 * 相當于創建了這樣一個向量{{Point(),Point()},{},{}} * */ vector<vector<Point>> contours; /* * hierarchy向量內每個元素保存了一個包含4個int整型的數組。向量hiararchy內的元素和輪廓向量contours內的元素是一一對應的,向量的容量相同。 * hierarchy向量內每一個元素的4個int型變量——hierarchy[i][0] ~ hierarchy[i][3],分別表示第i個輪廓的后一個輪廓、前一個輪廓、父輪廓、內嵌輪廓的索引編號。 * 如果當前輪廓沒有對應的后一個輪廓、前一個輪廓、父輪廓或內嵌輪廓的話,則hierarchy[i][0] ~ hierarchy[i][3]的相應位被設置為默認值-1。 * */ vector<Vec4i> hierarchy; /* * findContours找到輪廓 * 第一個參數:單通道圖像矩陣,可以是灰度圖,但更常用的是二值圖像,一般是經過Canny、拉普拉斯等邊緣檢測算子處理過的二值圖像; * 第二個參數:contours (前文介紹過) * 第三個參數:hierarchy(前文介紹過) * 第四個參數:輪廓的檢索模式 * 取值一:CV_RETR_EXTERNAL 只檢測最外圍輪廓,包含在外圍輪廓內的內圍輪廓被忽略 * 取值二:CV_RETR_LIST 檢測所有的輪廓,包括內圍、外圍輪廓,但是檢測到的輪廓不建立等級關系,彼此之間獨立,沒有等級關系,這就意味著這個檢索模式下不存在父輪廓或內嵌輪廓,所以hierarchy向量內所有元素的第3、第4個分量都會被置為-1,具體下文會講到 * 取值三:CV_RETR_CCOMP 檢測所有的輪廓,但所有輪廓只建立兩個等級關系,外圍為頂層,若外圍內的內圍輪廓還包含了其他的輪廓信息,則內圍內的所有輪廓均歸屬于頂層 * 取值四:CV_RETR_TREE 檢測所有輪廓,所有輪廓建立一個等級樹結構。外層輪廓包含內層輪廓,內層輪廓還可以繼續包含內嵌輪廓。 * 第五個參數:輪廓的近似方法 * 取值一:CV_CHAIN_APPROX_NONE 保存物體邊界上所有連續的輪廓點到contours向量內 * 取值二:CV_CHAIN_APPROX_SIMPLE 僅保存輪廓的拐點信息,把所有輪廓拐點處的點保存入contours向量內,拐點與拐點之間直線段上的信息點不予保留 * 取值三和四:CV_CHAIN_APPROX_TC89_L1,CV_CHAIN_APPROX_TC89_KCOS使用teh-Chinl chain 近似算法 * 第六個參數:Point偏移量,所有的輪廓信息相對于原始圖像對應點的偏移量,相當于在每一個檢測出的輪廓點上加上該偏移量,且Point可以是負值。不填為默認不偏移Point() * */ findContours(img_input, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE); /* * drawContours繪出輪廓 * 第一個參數:指明在哪幅圖像上繪制輪廓。image為三通道才能顯示輪廓 * 第二個參數:contours * 第三個參數:指定繪制哪條輪廓,如果是-1,則繪制其中的所有輪廓 * 第四個參數:輪廓線顏色 * 第五個參數:輪廓線的寬度,如果是-1(FILLED),則為填充 * */ // // 不全輸出,在下文只輸出角點 // drawContours(image, contours, -1, Scalar(255, 0, 255), 2); // 定義輪廓,大小與contours相同,但內層向量中只有角點(例如三角形就是3,四邊形就是4,圓形可能七八個) vector<vector<Point>> corners_contours(contours.size()); // 定義邊界框,大小與contours相同 vector<Rect> bounding_box(contours.size()); vector<Point> biggest_contours; double max_area = 0; for (int i = 0; i < contours.size(); i++) { // 檢測輪廓面積 double contour_area = contourArea(contours[i]); // cout << area << endl; // 假設圖像中有噪聲,需要將其過濾,只保留面積大于1000的輪廓 if (contour_area > 1000) { // 計算每個輪廓的周長 double contour_perimeter = arcLength(contours[i], true); // 使用DP算法計算出輪廓點的個數,規則為周長*0.02 approxPolyDP(contours[i], corners_contours[i], 0.02 * contour_perimeter, true); // 找到圖像中面積最大的,且角點為4的輪廓 if (contour_area > max_area && corners_contours[i].size() == 4 ) { //drawContours(image_origin, conPoly, i, Scalar(255, 0, 255), 5); biggest_contours = { corners_contours[i][0],corners_contours[i][1] ,corners_contours[i][2] ,corners_contours[i][3] }; max_area = contour_area; } // // 只繪制角點之間的邊框線,Debug用,取消注釋可以看到檢測出的所有邊界框 // drawContours(image_origin, corners_contours, i, Scalar(255, 0, 255), 2); // rectangle(image_origin, bounding_box[i].tl(), bounding_box[i].br(), Scalar(0, 255, 0), 5); } } // 返回最大的輪廓 return biggest_contours; } /* * 函數功能:繪制一些點 * 輸入:點集,顏色 * */ void DrawPoints(vector<Point> points, const Scalar& color) { for (int i = 0; i < points.size(); i++) { circle(image_origin, points[i], 10, color, FILLED); putText(image_origin, to_string(i), points[i], FONT_HERSHEY_PLAIN, 4, color, 4); } } /* * 函數功能:重新排列四個角點的順序 * 最終順序為: 0 1 * 2 3 * 數組中為左上角-右上角-左下角-右下角 * */ vector<Point> ReorderPoints(vector<Point> points) { vector<Point> newPoints; vector<int> sumPoints, subPoints; // OpenCV中左上頂點為(0,0),右為x軸正向,下為y軸正向。 for (int i = 0; i < 4; i++) { // 將每個點的xy坐標值相加(x+y),左上角的點的坐標和應該是最小的,右下角的點的坐標和應該是最大的 sumPoints.push_back(points[i].x + points[i].y); // 將每個點的xy坐標值相減(x-y),左下角的點的坐標差應該是最小的,右上角的點的坐標差應該是最大的 subPoints.push_back(points[i].x - points[i].y); } // 重新排列 newPoints.push_back(points[min_element(sumPoints.begin(), sumPoints.end()) - sumPoints.begin()]); // 0 和的最小值 newPoints.push_back(points[max_element(subPoints.begin(), subPoints.end()) - subPoints.begin()]); // 1 差的最大值 newPoints.push_back(points[min_element(subPoints.begin(), subPoints.end()) - subPoints.begin()]); // 2 差的最小值 newPoints.push_back(points[max_element(sumPoints.begin(), sumPoints.end()) - sumPoints.begin()]); // 3 和的最大值 return newPoints; } /* * 函數功能: * 輸入:源圖像,四個角點的集合(角點的順序為,左上角-右上角-左下角-右下角),輸出的寬,輸出的高 * 輸出:透視變換后的圖像 * */ Mat PerspectiveTrans(const Mat& img, vector<Point> points, float width, float height ) { // 前面經過重新排列,四個角點的順序為:左上角-右上角-左下角-右下角 Point2f src[4] = { points[0],points[1],points[2],points[3] }; // 變換后的四個角點 Point2f dst[4] = { {0.0f,0.0f},{width,0.0f},{0.0f,height},{width,height} }; // 創建變換矩陣 Mat matrix = getPerspectiveTransform(src, dst); // 透視變換 warpPerspective(img, image_trans, matrix, Point(width, height)); return image_trans; } int main() { // 1.讀取原始圖像 string path = "res/image_origin.jpg"; image_origin = imread(path); // // 若圖像太大可以先進行縮放處理 // resize(image_origin, image_origin, Size(), 0.5, 0.5); // 獲取原始圖像的寬和高 origin_width = image_origin.size().width; origin_height = image_origin.size().height; // 2.對圖像進行預處理得到邊緣,依次進行灰度處理、高斯模糊、邊緣檢測、膨脹、腐蝕。 image_preprocess = PreProcess(image_origin, 0); // 3.找到最大的輪廓,并提取角點 origin_points = GetMaxContour(image_preprocess); // DrawPoints(origin_points, Scalar(0, 0, 255)); // 紅色 // 此時發現,角點的順序不固定,為了后面進行透視變換時與代碼中變換后點集的順序相同,需要將其排列成一個固定的順序,排列后的順序為:左上角-右上角-左下角-右下角 reorder_points = ReorderPoints(origin_points); // DrawPoints(reorder_points, Scalar(0, 255, 0)); //綠色 // 4.透視變換 image_trans = PerspectiveTrans(image_origin, reorder_points, origin_width, origin_height); // 透視變換后有一些毛邊,若需要可以進行裁剪 // 四周裁剪5像素 int cropVal= 5; // 創建一個矩形用來裁剪 Rect roi(cropVal, cropVal, origin_width - (2 * cropVal), origin_height - (2 * cropVal)); image_crop = image_trans(roi); // 裁剪后重新調整比例 resize(image_crop, image_crop, Size(origin_width, origin_height)); // 5.顯示并輸出變換后圖像 imshow("源圖像", image_origin); imshow("最終圖像", image_crop); imwrite("res/image_output.jpg", image_crop); waitKey(0); }
關于“怎么用C++ OpenCV實現文檔矯正功能”這篇文章的內容就介紹到這里,感謝各位的閱讀!相信大家對“怎么用C++ OpenCV實現文檔矯正功能”知識都有一定的了解,大家如果還想學習更多知識,歡迎關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。