最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

深入探討C++ OpenCV如何實(shí)現(xiàn)圖像矯正

 更新時(shí)間:2024年03月06日 10:10:19   作者:最難不過二叉樹  
這篇文章主要為大家詳細(xì)介紹了C++ OpenCV如何實(shí)現(xiàn)簡(jiǎn)單的圖像矯正功能,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下

剛進(jìn)入實(shí)驗(yàn)室導(dǎo)師就交給我一個(gè)任務(wù),就是讓我設(shè)計(jì)算法給圖像進(jìn)行矯正。哎呀,我不太會(huì)圖像這塊啊,不過還是接下來了,硬著頭皮開干吧!

那什么是圖像的矯正呢?舉個(gè)例子就好明白了。

我的好朋友小明給我拍了這幾張照片,因?yàn)樗呐恼占夹g(shù)不咋地,照片都拍得歪歪扭扭的,比如下面這些照片:

發(fā)票

文本

這些圖片讓人看得真不舒服!看個(gè)圖片還要歪脖子看,實(shí)在是太煩人了!我叫小明幫我掃描一下一本教科書,小明把每一頁書都拍成上面的文本那樣了。好氣啊那該怎么辦呢?一頁一頁用PS來處理?1000頁的矯正啊,當(dāng)然交給計(jì)算機(jī)去做!

真的,對(duì)于圖像矯正的問題,在圖像處理領(lǐng)域還真得多,比如人民幣的矯正、文本的矯正、車牌的矯正、身份證矯正等等。這些都是因?yàn)榕臄z者總不可能100%正確地拍攝好圖片,這就要求我們通過后期的圖像處理技術(shù)將圖片還原好,才能進(jìn)一步做后面的處理,比如數(shù)字分割啊數(shù)字識(shí)別啊,不然歪歪扭扭的文字?jǐn)?shù)字,想識(shí)別出來估計(jì)就很難了。

上面幾個(gè)圖,我們?cè)谌粘I钪杏龅降目刹簧?,因?yàn)榕臄z時(shí)拍的不好,導(dǎo)致拍出來的圖片歪歪扭扭的,很不自然,那么我們能不能把這些圖片盡可能地矯正過來呢?

OpenCV告訴我們,沒問題!工具我給你,算法你自己設(shè)計(jì)!

比如圖一該怎么做?那就涉及到了圖像的矯正和感興趣區(qū)域提取兩大技術(shù)了。

總的來說,要進(jìn)行進(jìn)行圖像矯正,至少有以下幾項(xiàng)知識(shí)儲(chǔ)備:

  • 輪廓提取技術(shù)
  • 霍夫變換知識(shí)
  • ROI感興趣區(qū)域知識(shí)

下面以發(fā)票矯正、文本矯正為例,一步步剖析如何實(shí)現(xiàn)圖像矯正。

比如我們要矯正這張圖片,思路應(yīng)該是怎么樣?

首先分析這張圖的特點(diǎn)。

在這張圖里,物體有一定的傾斜角度,但是角度不大;背景是黑色的,而且物體邊緣應(yīng)該比較明顯。

沒錯(cuò),我們就抓住邊緣比較明顯來做文章!我們是不是可以先把輪廓找出來(找出來的輪廓當(dāng)然就是一個(gè)大大的矩形),然后用矩形去包圍它,得到他的旋轉(zhuǎn)角度,然后根據(jù)得到的角度進(jìn)行旋轉(zhuǎn),那樣不就可以實(shí)現(xiàn)矯正了嗎!

再詳細(xì)地總結(jié)處理步驟:

  • 圖片灰度化
  • 閾值二值化
  • 檢測(cè)輪廓
  • 尋找輪廓的包圍矩陣,并且獲取角度
  • 根據(jù)角度進(jìn)行旋轉(zhuǎn)矯正
  • 對(duì)旋轉(zhuǎn)后的圖像進(jìn)行輪廓提取
  • 對(duì)輪廓內(nèi)的圖像區(qū)域摳出來,成為一張獨(dú)立圖像

我把該矯正算法命名為基于輪廓提取的矯正算法,因?yàn)槠潢P(guān)鍵技術(shù)就是通過輪廓來獲取旋轉(zhuǎn)角度。

#include "opencv2/imgproc.hpp"
#include "opencv2/highgui.hpp"
#include <iostream>
using namespace cv;
using namespace std;

//第一個(gè)參數(shù):輸入圖片名稱;第二個(gè)參數(shù):輸出圖片名稱
void GetContoursPic(const char* pSrcFileName, const char* pDstFileName)
{
	Mat srcImg = imread(pSrcFileName);
	imshow("原始圖", srcImg);
	Mat gray, binImg;
	//灰度化
	cvtColor(srcImg, gray, COLOR_RGB2GRAY);
	imshow("灰度圖", gray);
	//二值化
	threshold(gray, binImg, 100, 200, CV_THRESH_BINARY);
	imshow("二值化", binImg);

	vector<vector<Point> > contours;
	vector<Rect> boundRect(contours.size());
	//注意第5個(gè)參數(shù)為CV_RETR_EXTERNAL,只檢索外框  
	findContours(binImg, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE); //找輪廓
	cout << contours.size() << endl;
	for (int i = 0; i < contours.size(); i++)
	{
		//需要獲取的坐標(biāo)  
		CvPoint2D32f rectpoint[4];
		CvBox2D rect =minAreaRect(Mat(contours[i]));

		cvBoxPoints(rect, rectpoint); //獲取4個(gè)頂點(diǎn)坐標(biāo)  
		//與水平線的角度  
		float angle = rect.angle;
		cout << angle << endl;

		int line1 = sqrt((rectpoint[1].y - rectpoint[0].y)*(rectpoint[1].y - rectpoint[0].y) + (rectpoint[1].x - rectpoint[0].x)*(rectpoint[1].x - rectpoint[0].x));
		int line2 = sqrt((rectpoint[3].y - rectpoint[0].y)*(rectpoint[3].y - rectpoint[0].y) + (rectpoint[3].x - rectpoint[0].x)*(rectpoint[3].x - rectpoint[0].x));
		//rectangle(binImg, rectpoint[0], rectpoint[3], Scalar(255), 2);
		//面積太小的直接pass
		if (line1 * line2 < 600)
		{
			continue;
		}

		//為了讓正方形橫著放,所以旋轉(zhuǎn)角度是不一樣的。豎放的,給他加90度,翻過來  
		if (line1 > line2) 
		{
			angle = 90 + angle;
		}

		//新建一個(gè)感興趣的區(qū)域圖,大小跟原圖一樣大  
		Mat RoiSrcImg(srcImg.rows, srcImg.cols, CV_8UC3); //注意這里必須選CV_8UC3
		RoiSrcImg.setTo(0); //顏色都設(shè)置為黑色  
		//imshow("新建的ROI", RoiSrcImg);
		//對(duì)得到的輪廓填充一下  
		drawContours(binImg, contours, -1, Scalar(255),CV_FILLED);

		//摳圖到RoiSrcImg
		srcImg.copyTo(RoiSrcImg, binImg);


		//再顯示一下看看,除了感興趣的區(qū)域,其他部分都是黑色的了  
		namedWindow("RoiSrcImg", 1);
		imshow("RoiSrcImg", RoiSrcImg);

		//創(chuàng)建一個(gè)旋轉(zhuǎn)后的圖像  
		Mat RatationedImg(RoiSrcImg.rows, RoiSrcImg.cols, CV_8UC1);
		RatationedImg.setTo(0);
		//對(duì)RoiSrcImg進(jìn)行旋轉(zhuǎn)  
		Point2f center = rect.center;  //中心點(diǎn)  
		Mat M2 = getRotationMatrix2D(center, angle, 1);//計(jì)算旋轉(zhuǎn)加縮放的變換矩陣 
		warpAffine(RoiSrcImg, RatationedImg, M2, RoiSrcImg.size(),1, 0, Scalar(0));//仿射變換 
		imshow("旋轉(zhuǎn)之后", RatationedImg);
		imwrite("r.jpg", RatationedImg); //將矯正后的圖片保存下來
	}

#if 1
	//對(duì)ROI區(qū)域進(jìn)行摳圖

	//對(duì)旋轉(zhuǎn)后的圖片進(jìn)行輪廓提取  
	vector<vector<Point> > contours2;
	Mat raw = imread("r.jpg");
	Mat SecondFindImg;
	//SecondFindImg.setTo(0);
	cvtColor(raw, SecondFindImg, COLOR_BGR2GRAY);  //灰度化  
	threshold(SecondFindImg, SecondFindImg, 80, 200, CV_THRESH_BINARY);
	findContours(SecondFindImg, contours2, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE);
	//cout << "sec contour:" << contours2.size() << endl;

	for (int j = 0; j < contours2.size(); j++)
	{
		//這時(shí)候其實(shí)就是一個(gè)長方形了,所以獲取rect  
		Rect rect = boundingRect(Mat(contours2[j]));
		//面積太小的輪廓直接pass,通過設(shè)置過濾面積大小,可以保證只拿到外框
		if (rect.area() < 600)
		{
			continue;
		}
		Mat dstImg = raw(rect);
		imshow("dst", dstImg);
		imwrite(pDstFileName, dstImg);
	}
#endif


}


void main()
{
	GetContoursPic("6.jpg", "FinalImage.jpg");
	waitKey();
}

效果如下:

原始圖

傾斜矯正之后

最后把目標(biāo)區(qū)域摳出來,成為單獨(dú)的照片。

上面的算法可以很好的處理發(fā)票的傾斜矯正,那文本矯正可以嗎?我趕緊試了一下,結(jié)果是失敗的。

原圖

算法矯正后,還是原樣,矯正失敗。

認(rèn)真分析一下,還是很容易看出文本矯正失敗的原因的。

原因就在于,發(fā)票圖像他們有明顯的的邊界輪廓,而文本圖像沒有。文本圖像的背景是白色的,所以我們沒有辦法像人民幣發(fā)票那類有明顯邊界的矩形物體那樣,提取出輪廓并旋轉(zhuǎn)矯正。

經(jīng)過深入分析可以看出,雖然文本類圖像沒有明顯的邊緣輪廓,但是他們有一個(gè)很重要的特征,那就是每一行文字都是呈現(xiàn)一條直線形狀,而且這些直線都是平行的!

對(duì)于這種情況,我想到了另一種方法:基于直線探測(cè)的矯正算法

首先介紹一下我的算法思路:

  • 用霍夫線變換探測(cè)出圖像中的所有直線
  • 計(jì)算出每條直線的傾斜角,求他們的平均值
  • 根據(jù)傾斜角旋轉(zhuǎn)矯正
  • 最后根據(jù)文本尺寸裁剪圖片

然后給出OpenCV的實(shí)現(xiàn)算法:

#include "opencv2/imgproc.hpp"
#include "opencv2/highgui.hpp"
#include <iostream>
using namespace cv;
using namespace std;

#define ERROR 1234

//度數(shù)轉(zhuǎn)換
double DegreeTrans(double theta)
{
	double res = theta / CV_PI * 180;
	return res;
}


//逆時(shí)針旋轉(zhuǎn)圖像degree角度(原尺寸)    
void rotateImage(Mat src, Mat& img_rotate, double degree)
{
	//旋轉(zhuǎn)中心為圖像中心    
	Point2f center;
	center.x = float(src.cols / 2.0);
	center.y = float(src.rows / 2.0);
	int length = 0;
	length = sqrt(src.cols*src.cols + src.rows*src.rows);
	//計(jì)算二維旋轉(zhuǎn)的仿射變換矩陣  
	Mat M = getRotationMatrix2D(center, degree, 1);
	warpAffine(src, img_rotate, M, Size(length, length), 1, 0, Scalar(255,255,255));//仿射變換,背景色填充為白色  
}

//通過霍夫變換計(jì)算角度
double CalcDegree(const Mat &srcImage, Mat &dst)
{
	Mat midImage, dstImage;

	Canny(srcImage, midImage, 50, 200, 3);
	cvtColor(midImage, dstImage, CV_GRAY2BGR);

	//通過霍夫變換檢測(cè)直線
	vector<Vec2f> lines;
	HoughLines(midImage, lines, 1, CV_PI / 180, 300, 0, 0);//第5個(gè)參數(shù)就是閾值,閾值越大,檢測(cè)精度越高
	//cout << lines.size() << endl;

	//由于圖像不同,閾值不好設(shè)定,因?yàn)殚撝翟O(shè)定過高導(dǎo)致無法檢測(cè)直線,閾值過低直線太多,速度很慢
	//所以根據(jù)閾值由大到小設(shè)置了三個(gè)閾值,如果經(jīng)過大量試驗(yàn)后,可以固定一個(gè)適合的閾值。

	if (!lines.size())
	{
		HoughLines(midImage, lines, 1, CV_PI / 180, 200, 0, 0);
	}
	//cout << lines.size() << endl;

	if (!lines.size())
	{
		HoughLines(midImage, lines, 1, CV_PI / 180, 150, 0, 0);
	}
	//cout << lines.size() << endl;
	if (!lines.size())
	{
		cout << "沒有檢測(cè)到直線!" << endl;
		return ERROR;
	}

	float sum = 0;
	//依次畫出每條線段
	for (size_t i = 0; i < lines.size(); i++)
	{
		float rho = lines[i][0];
		float theta = lines[i][1];
		Point pt1, pt2;
		//cout << theta << endl;
		double a = cos(theta), b = sin(theta);
		double x0 = a*rho, y0 = b*rho;
		pt1.x = cvRound(x0 + 1000 * (-b));
		pt1.y = cvRound(y0 + 1000 * (a));
		pt2.x = cvRound(x0 - 1000 * (-b));
		pt2.y = cvRound(y0 - 1000 * (a));
		//只選角度最小的作為旋轉(zhuǎn)角度
		sum += theta;

		line(dstImage, pt1, pt2, Scalar(55, 100, 195), 1, LINE_AA); //Scalar函數(shù)用于調(diào)節(jié)線段顏色

		imshow("直線探測(cè)效果圖", dstImage);
	}
	float average = sum / lines.size(); //對(duì)所有角度求平均,這樣做旋轉(zhuǎn)效果會(huì)更好

	cout << "average theta:" << average << endl;

	double angle = DegreeTrans(average) - 90;

	rotateImage(dstImage, dst, angle);
	//imshow("直線探測(cè)效果圖2", dstImage);
	return angle;
}


void ImageRecify(const char* pInFileName, const char* pOutFileName)
{
	double degree;
	Mat src = imread(pInFileName);
	imshow("原始圖", src);
	Mat dst;
	//傾斜角度矯正
	degree = CalcDegree(src,dst);
	if (degree == ERROR)
	{
		cout << "矯正失??!" << endl;
		return;
	}
	rotateImage(src, dst, degree);
	cout << "angle:" << degree << endl;
	imshow("旋轉(zhuǎn)調(diào)整后", dst);

	Mat resulyImage = dst(Rect(0, 0, dst.cols, 500)); //根據(jù)先驗(yàn)知識(shí),估計(jì)好文本的長寬,再裁剪下來
	imshow("裁剪之后", resulyImage);
	imwrite("recified.jpg", resulyImage); 
}


int main()
{
	ImageRecify("correct2.jpg", "FinalImage.jpg");
	waitKey();
	return 0;
}

看看效果。這是原始圖

直線探測(cè)的效果。

矯正之后的效果。

我們發(fā)現(xiàn)矯正之后的圖像有較多留白,影響觀看,所以需要進(jìn)一步裁剪,保留文字區(qū)域。

趕緊再試多一張。

原始圖

直線探測(cè)

矯正效果

進(jìn)一步裁剪

可以看出,基于直線探測(cè)的矯正算法在文本處理上效果真的很不錯(cuò)!

最后總結(jié)一下兩個(gè)算法的應(yīng)用場(chǎng)景:

  • 基于輪廓提取的矯正算法更適用于車牌、身份證、人民幣、書本、發(fā)票一類矩形形狀而且邊界明顯的物體矯正。
  • 基于直線探測(cè)的矯正算法更適用于文本類的矯正。

以上就是深入探討C++ OpenCV如何實(shí)現(xiàn)圖像矯正的詳細(xì)內(nèi)容,更多關(guān)于OpenCV圖像矯正的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • C語言實(shí)現(xiàn)24位彩色圖像二值化

    C語言實(shí)現(xiàn)24位彩色圖像二值化

    這篇文章主要為大家詳細(xì)介紹了C語言實(shí)現(xiàn)24位彩色圖像二值化,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-10-10
  • 淺談c++ stl迭代器失效的問題

    淺談c++ stl迭代器失效的問題

    下面小編就為大家?guī)硪黄獪\談c++ stl迭代器失效的問題。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧,祝大家游戲愉快哦
    2016-12-12
  • C++ 配置文件管理神器 yaml-cpp 實(shí)戰(zhàn)指南

    C++ 配置文件管理神器 yaml-cpp 實(shí)戰(zhàn)指南

    本文主要介紹了C++ 配置文件管理神器 yaml-cpp 實(shí)戰(zhàn)指南,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2026-05-05
  • VS2010+Opencv+MFC讀取圖像和視頻顯示在Picture控件

    VS2010+Opencv+MFC讀取圖像和視頻顯示在Picture控件

    這篇文章主要為大家詳細(xì)介紹了VS2010+Opencv+MFC讀取圖像和視頻顯示在Picture控件,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2019-08-08
  • C語言實(shí)現(xiàn)猜拳游戲

    C語言實(shí)現(xiàn)猜拳游戲

    這篇文章主要為大家詳細(xì)介紹了C語言實(shí)現(xiàn)猜拳游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2020-02-02
  • VC++實(shí)現(xiàn)選擇排序算法簡(jiǎn)單示例

    VC++實(shí)現(xiàn)選擇排序算法簡(jiǎn)單示例

    這篇文章主要介紹了VC++實(shí)現(xiàn)選擇排序算法簡(jiǎn)單示例,代碼簡(jiǎn)潔易懂,有助于讀者對(duì)數(shù)據(jù)結(jié)構(gòu)與算法的學(xué)習(xí),需要的朋友可以參考下
    2014-08-08
  • C語言數(shù)據(jù)類型和格式說明符基礎(chǔ)教程示例

    C語言數(shù)據(jù)類型和格式說明符基礎(chǔ)教程示例

    這篇文章主要為大家介紹了C語言數(shù)據(jù)類型和格式說明符基礎(chǔ)教程示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-12-12
  • C語言實(shí)現(xiàn)手寫JSON解析的方法詳解

    C語言實(shí)現(xiàn)手寫JSON解析的方法詳解

    JSON(JavaScript?Object?Notation)是一種輕量級(jí)的數(shù)據(jù)交換格式,用來傳輸屬性值或者序列性的值組成的數(shù)據(jù)對(duì)象。本文將利用C語言實(shí)現(xiàn)手寫JSON解析,感興趣的可以了解一下
    2022-09-09
  • C語言實(shí)現(xiàn)簡(jiǎn)易撲克牌游戲

    C語言實(shí)現(xiàn)簡(jiǎn)易撲克牌游戲

    這篇文章主要為大家詳細(xì)介紹了C語言實(shí)現(xiàn)簡(jiǎn)易撲克牌游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2019-10-10
  • C語言實(shí)現(xiàn)數(shù)獨(dú)游戲

    C語言實(shí)現(xiàn)數(shù)獨(dú)游戲

    這篇文章主要為大家詳細(xì)介紹了C語言實(shí)現(xiàn)數(shù)獨(dú)游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-03-03

最新評(píng)論

忻城县| 南阳市| 四子王旗| 延川县| 敦化市| 五大连池市| 连南| 津南区| 马公市| 日照市| 如东县| 霍邱县| 乌拉特前旗| 文化| 丽江市| 宁海县| 平武县| 封丘县| 厦门市| 苗栗市| 湖南省| 巴马| 崇礼县| 东兴市| 泸州市| 珲春市| 尤溪县| 安阳县| 柯坪县| 松桃| 林口县| 公主岭市| 浦东新区| 双城市| 沽源县| 莫力| 高陵县| 凤城市| 石柱| 那坡县| 文昌市|