<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
將一個斜著拍攝的檔案矯正成正的,如圖所示:
1.讀取原始影象,若影象太大可以先進行縮放處理,並獲取原始影象的寬和高
2.對影象進行預處理得到邊緣,依次進行灰度處理、高斯模糊、邊緣檢測、膨脹、腐蝕。
3.找到最大的輪廓,並提取角點
4.將找到的四個角點排列成一個固定的順序,排列後的順序為:左上角-右上角-左下角-右下角
5.進行透視變換
6.若透視變換後有一些毛邊,按需要進行裁剪,裁剪後重新調整比例
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實現檔案矯正功能的文章就介紹到這了,更多相關OpenCV檔案矯正內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45