91超碰碰碰碰久久久久久综合_超碰av人澡人澡人澡人澡人掠_国产黄大片在线观看画质优化_txt小说免费全本

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

怎么用C++?OpenCV實現文檔矯正功能

發布時間:2022-03-16 09:09:14 來源:億速云 閱讀:461 作者:iii 欄目:開發技術

這篇文章主要介紹了怎么用C++ OpenCV實現文檔矯正功能的相關知識,內容詳細易懂,操作簡單快捷,具有一定借鑒價值,相信大家閱讀完這篇怎么用C++ OpenCV實現文檔矯正功能文章都會有所收獲,下面我們一起來看看吧。

需求

將一個斜著拍攝的文檔矯正成正的,如圖所示:

怎么用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實現文檔矯正功能”這篇文章的內容就介紹到這里,感謝各位的閱讀!相信大家對“怎么用C++ OpenCV實現文檔矯正功能”知識都有一定的了解,大家如果還想學習更多知識,歡迎關注億速云行業資訊頻道。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

大城县| 曲沃县| 阿拉尔市| 延寿县| 南平市| 邹平县| 上栗县| 静海县| 彭山县| 松溪县| 武定县| 太康县| 乌苏市| 毕节市| 鄯善县| 林西县| 信丰县| 柘荣县| 禹州市| 五指山市| 临江市| 舞钢市| 敦煌市| 昌吉市| 陇南市| 革吉县| 万山特区| 石阡县| 夏津县| 信丰县| 固阳县| 台南县| 越西县| 安阳市| 马公市| 石首市| 阳原县| 湘潭县| 抚宁县| 赣榆县| 银川市|