要進行圖像矯正,至少需要具備以下知識儲備:
- 輪廓提取技術
- 霍夫變換知識
- ROI感興趣區域知識
下面以人民幣矯正、發票矯正、文本矯正爲例,一步步剖析如何實現圖像矯正。
首先分析如何矯正人民幣。
比如我們要矯正這張人民幣,思路應該是怎麼樣的?
首先分析這張圖的特點。
在這張圖裏,人民幣有一定的傾斜角度,但是角度不大;人民幣的背景是黑色的,而且人命幣的邊緣比較明顯。
沒錯,我們抓住人民幣的邊緣比較明顯來做文章!我們可以先把人民幣的輪廓找出來(找出來的輪廓當然是一個大大的矩形),然後用矩形去包圍它,得到他的旋轉角度,然後根據得到的角度進行旋轉,就可以實現矯正了!
詳細的總結處理步驟:
- 圖片灰度化
- 閾值二值化
- 檢測輪廓
- 尋找輪廓的包圍矩陣,並且獲取角度
- 根據角度進行旋轉矯正
- 對旋轉後的圖像進行輪廓提取
- 對輪廓內的圖像區域摳出來,成爲一張獨立圖像
我們把該矯正算法命名爲基於輪廓提取的矯正算法,因爲其關鍵技術是通過輪廓來獲取旋轉角度。
#include "opencv2/imgproc.hpp"
#include "opencv2/highgui.hpp"
#include <iostream>
using namespace cv;
using namespace std;
//第一個參數:輸入圖片名稱: 第二參數:輸出圖片名稱
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個參數爲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++)
{
//需要獲取的座標
CvPoint2D32f rectpoint[4];
CvBox2D rect = minAreaRect(Mat(contours[i]));
cvBoxPoints(rect, rectpoint);//獲取4個頂點座標
//與水平線的角度
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;
}
//爲了讓正方形橫着放,所以旋轉角度是不一樣的,豎放的,給他加90度,翻過來
if (line1 > line2)
{
angle = 90 + angle;
}
//新建一個感興趣的區域圖,大小跟原圖一樣大
Mat RoiSrcImg(srcImg.rows, srcImg.cols, CV_8UC3);//注意這裏必須選CV_8UC3
RoiSrcImg.setTo(0);//顏色都設置爲黑色
//對得到的輪廓填充一下
drawContours(binImg, contours, -1, Scalar(255), CV_FILLED);
//摳圖到RoiSrcImg
srcImg.copyTo(RoiSrcImg, binImg);
//再顯示一下看看,除了感興趣的區域,其他部分都是黑色的了
namedWindow("RoiSrcImg", 1);
imshow("RoiSrcImg", RoiSrcImg);
//創建一個旋轉後的圖像
Mat RatationedImg(RoiSrcImg.rows, RoiSrcImg.cols, CV_8UC1);
RatationedImg.setTo(0);
//對RoiSrcImg進行旋轉
Point2f center = rect.center;//中心點
Mat M2 = getRotationMatrix2D(center, angle, 1);//計算旋轉加縮放的變換矩陣
warpAffine(RoiSrcImg, RatationedImg, M2, RoiSrcImg.size(), 1, 0, Scalar(0));//仿射變換
imshow("旋轉之後", RatationedImg);
imwrite("r.jpg", RatationedImg);//將矯正後的圖片保存下來
}
//#if 1
// code
//#endif爲測試程序
#if 1
//對ROI區域進行摳圖
//對旋轉後的圖片進行輪廓提取
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++)
{
//這時候其實就是一個長方形了,所以獲取rect
Rect rect = boundingRect(Mat(contours2[j]));
//面積太小的輪廓直接pass,通過設置過濾面積大小,可以保證只拿到外框
if (rect.area() < 600)
{
continue;
}
Mat dstImg = raw(rect);
imshow("dst", dstImg);
//imwrite(pDstFileName, dstImg);
}
#endif
}
void main()
{
GetContoursPic("E:\\OpenCV\\ImageCorrection\\2.jpg", "FinalImage,jpg");
waitKey();
}
效果一次如下:
原始圖
二值化圖
旋轉矯正後
將人民幣區域摳出來
該算法效果還是很不錯的,那趕緊試試其他圖片,我們把傾斜的發票圖像拿去試試
原始圖
傾斜矯正後
最後把目標區域摳出來,成爲單獨的照片。