目錄
1.2 基于傅里葉變換以及霍夫直線檢測(cè)的旋轉(zhuǎn)矯正
2.2.2 利用得到的二階距計(jì)算圖像偏轉(zhuǎn)角度
2.2.3 利用仿射變換對(duì)圖像進(jìn)行旋轉(zhuǎn)矯正
系統(tǒng)環(huán)境 Windows 10 64 位 + OpenCV 3.4.1 64 位
1、常見(jiàn)圖像旋轉(zhuǎn)矯正方法
常見(jiàn)的圖像旋轉(zhuǎn)矯正方式有:基于圖像邊緣輪廓的旋轉(zhuǎn)矯正和基于傅里葉變換以及霍夫直線檢測(cè)的旋轉(zhuǎn)矯正兩種方法。
1.1 基于圖像邊緣輪廓的旋轉(zhuǎn)矯正
1)圖像灰度化
2)閾值二值化
3)檢測(cè)輪廓
4)提取輪廓的包圍矩陣(圖像前景掩膜)
5)通過(guò)提取的包圍矩陣獲取偏轉(zhuǎn)角度
6)利用仿射變換對(duì)圖像進(jìn)行偏轉(zhuǎn)
具體實(shí)現(xiàn)參考:OpenCV探索之路(十六):圖像矯正技術(shù)深入探討
1.2 基于傅里葉變換以及霍夫直線檢測(cè)的旋轉(zhuǎn)矯正
1)利用DTF變換計(jì)算圖像的頻譜圖
2)頻譜中心移動(dòng)
3)對(duì)移動(dòng)后的頻譜圖進(jìn)行二值分割
4)利用霍夫直線檢測(cè)計(jì)算圖像傾斜角度
5)利用仿射變換對(duì)圖像進(jìn)行旋轉(zhuǎn)矯正
具體實(shí)現(xiàn)參考:OpenCV實(shí)現(xiàn)基于傅里葉變換的旋轉(zhuǎn)文本校正
傅里葉變換的原理解析參考:傅里葉分析之掐死教程(完整版)和OpenCV圖像的傅里葉變換-(補(bǔ)番)
2惠赫、基于Hu距圖像旋轉(zhuǎn)矯正
除了以上兩種方式可以實(shí)現(xiàn)圖像旋轉(zhuǎn)矯正外颁褂,還可以利用HU距旋轉(zhuǎn)不變性對(duì)圖像進(jìn)行旋轉(zhuǎn)矯正。
2.1 Hu旋轉(zhuǎn)不變性
圖片經(jīng)過(guò)任意角度旋轉(zhuǎn)检盼、任意比例縮放Hu矩都保持不變肯污,即圖像Hu 矩的平移、旋轉(zhuǎn)不變性。Hu旋轉(zhuǎn)不變性詳情參考:圖像不變性特征—hu矩蹦渣、圖像特征_圖像矩(Hu矩)哄芜。七個(gè)不變矩由二階和三階中心矩的線性組合構(gòu)成,具體表達(dá)式如下:
?
在實(shí)際應(yīng)用中,在對(duì)圖片中物體進(jìn)行處理時(shí)柬唯,只有 M1 和 M2 體現(xiàn)良好的不變性认臊,其余的幾個(gè)不變矩產(chǎn)生的誤差比較大。因此锄奢,認(rèn)為只有基于二階矩的不變矩對(duì)二維物體的描述具有良好的旋轉(zhuǎn)失晴、縮放和平移不變性(M1和 M2 由二階矩組成)。
圖像的二階矩表示圖像的主軸拘央,二階矩值的大小可以確定圖像的長(zhǎng)軸和短軸涂屁,以此來(lái)計(jì)算軸的方向角度。最后灰伟,用主軸相對(duì)于法線軸的逆時(shí)針旋轉(zhuǎn)角度來(lái)近似圖像的旋轉(zhuǎn)方向拆又。
2.2 實(shí)現(xiàn)步驟
2.2.1 分別計(jì)算圖像二階距
其中,I(x,y)表示圖像在點(diǎn)(x,y)處的灰度值栏账,cx帖族,cy表示圖像的質(zhì)心坐標(biāo)。
2.2.2 利用得到的二階距計(jì)算圖像偏轉(zhuǎn)角度
2.2.3 利用仿射變換對(duì)圖像進(jìn)行旋轉(zhuǎn)矯正
2.4 程序?qū)崿F(xiàn)
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/opencv.hpp>
#include<iostream>
using namespace std;
using namespace cv;
//計(jì)算圖像質(zhì)心坐標(biāo)
bool calCentroid(Mat srcImg, int width, int height, int *x, int *y)
{
if (srcImg.empty())
{
return false;
}
int i, j;
long long m00 = 0, m10 = 0, m01 = 0;
for (i = 0; i < height; i++)
{
for (j = 0; j < width; j++)
{
uchar tmp = srcImg.at<uchar>(i, j);
m00 += tmp;
m10 += tmp * j;
m01 += tmp * i;
}
}
if (m00 != 0)
{
*x = m10 / m00;
*y = m01 / m00;
//cout <<"x:"<<*x<< endl;
//cout << "y:" << *y << endl;
}
else
{
*x = 0;
*y = 0;
return false;
}
return true;
}
//計(jì)算偏轉(zhuǎn)角度
double getAngle(Mat srcImg)
{
double angle = 0;
long long m00 = 0, m11 = 0, m12 = 0, m22 = 0;
int Rho11 = 0, Rho12 = 0, Rho22 = 0;
int gx = 0, gy = 0;
//計(jì)算圖像偏轉(zhuǎn)角度
if (calCentroid(srcImg, srcImg.cols, srcImg.rows, &gx, &gy) == true)//計(jì)算圖像質(zhì)心
{
//cout << "gx:" << gx << endl;
//cout << "gy:" << gy << endl;
//歸一化二階中心矩
for (int i = 0; i < srcImg.rows; i++)
{
for (int j = 0; j < srcImg.cols; j++)
{
uchar tmp = srcImg.at<uchar>(i, j);
m00 = m00 + tmp;
m12 = m12 + (i - gy)*(j - gx)*tmp;
m11 = m11 + (i - gy)*(i - gy)*tmp;
m22 = m22 + (j - gx)*(j - gx)*tmp;
}
}
Rho11 = m11 / m00;
Rho12 = m12 / m00;
Rho22 = m22 / m00;
}
float tempx = 0, tempy = 0;
//計(jì)算偏轉(zhuǎn)角度
if (Rho11 > Rho22)
{
tempy = Rho11 - Rho22 + sqrt((float)((Rho11 - Rho22)*(Rho11 - Rho22) + 4 * Rho12*Rho12));
tempx = (float)(-2 * Rho12);
angle = atan(tempy / tempx) * 180 / CV_PI;
}
else
{
tempy = (float)(-2 * Rho12);
tempx = Rho22 - Rho11 + sqrt((float)((Rho22 - Rho11)*(Rho22 - Rho11) + 4 * Rho12*Rho12));
angle = atan(tempy / tempx) * 180 / CV_PI;
}
//偏轉(zhuǎn)角度范圍:-45=< angle <=45
if (angle >= -45 && angle <= 45)
{
angle = angle;
}
else if (angle < -45)
{
angle = -(90 + angle);
}
else if (angle > 45)
{
angle = 90 - angle;
}
return angle;
}
//對(duì)圖像進(jìn)行旋轉(zhuǎn)矯正
bool ContoursCorrection(Mat srcImg, Mat &dstImage)
{
if (srcImg.empty())
{
return false;
}
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)
//與水平線的角度
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));
//面積太小的直接pass
if (line1 * line2 < 600)
{
continue;
}
//新建一個(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);
////計(jì)算偏轉(zhuǎn)角度
double angle = getAngle(binImg);
cout <<"angle:"<<angle<< endl;
//為了讓正方形橫著放,所以旋轉(zhuǎn)角度是不一樣的了讨。豎放的捻激,給他加90度,翻過(guò)來(lái)
if (line1 > line2)
{
angle = 90 + angle;
}
//再顯示一下看看前计,除了感興趣的區(qū)域胞谭,其他部分都是黑色的了
namedWindow("RoiSrcImg", 1);
imshow("RoiSrcImg", RoiSrcImg);
//對(duì)RoiSrcImg進(jìn)行旋轉(zhuǎn)
Point2f center = rect.center; //中心點(diǎn)
Mat M2 = getRotationMatrix2D(center, angle, 1);//計(jì)算旋轉(zhuǎn)加縮放的變換矩陣
warpAffine(RoiSrcImg, dstImage, M2, RoiSrcImg.size(), 1, 0, Scalar(0));//仿射變換
imshow("旋轉(zhuǎn)之后", dstImage);
}
return true;
}
int main(int argc, char *argv[])
{
//讀取輸入圖像
Mat input = imread("src.png", IMREAD_COLOR);
Mat out(input.size(),CV_8UC3,Scalar(0,0,0));
ContoursCorrection(input, out);
waitKey(0);
system("pause");
return 0;
}
2.5 旋轉(zhuǎn)矯正效果驗(yàn)證
從上圖中可以發(fā)現(xiàn),旋轉(zhuǎn)矯正后圖像由傾斜轉(zhuǎn)換成為水平男杈。
公眾號(hào)《Slater》回復(fù)“機(jī)器學(xué)習(xí)”即可免費(fèi)獲取一份機(jī)器學(xué)習(xí)資料丈屹。