之前熟悉了openCV怎么调整图片对比度和亮度(通过线性和非线性的方法),都是很基础的图形操作,这边开始介绍openCV图像处理很重要的工具方法:傅里叶变换。
这部分内容涉及到复杂的傅里叶公式、也涉及到很多数学上的原理,单单通过官网是根本不会理解这部分内容的,我也是翻阅了很多的资料才对这部分内容有了比较清晰的了解,这边我通过用openCV的傅里叶变换方法实现图片校正的功能,里面涉及到的方法和原理我尽量详细的介绍。
上一篇内容:http://blog.csdn.net/jbl20078/article/details/78854660
Mat mat = [[CVUtil sharedHelper]cvMatFromUIImage:_buildImg.image];
Mat grayImg;
cvtColor(mat, grayImg, COLOR_BGR2GRAY);
Mat padded;
int m = getOptimalDFTSize(grayImg.rows);
int n = getOptimalDFTSize(grayImg.cols);
//通过copyMakeBorder进行填充像素和颜色(多出的像素点全部用0填充)
copyMakeBorder(grayImg, padded, 0, m-grayImg.rows, 0, n-grayImg.cols, BORDER_CONSTANT, Scalar::all(0));
//傅里叶变化后的结果是一个复数,也就是转化到频域中会有两个图像值,我们将图像转化成float类型 并且添加一个额外通道来存储复数部分
Mat planes[] = {Mat_(padded),Mat::zeros(padded.size(), CV_32F)}; //复制了一个padded,然后添加了一个全是0的通道
Mat complex;
//进行通道的混合
//merge函数是合并多个array 成为一个多通道的array,比方说array1 array2 合并成array3 那么array3[1][0] = array1[1]
//array3[1][2] = array2[1] 它的逆向操作是split方法
merge(planes,2,complex);
//合并好了 相当于给傅里叶变换的结果预先分配了存储空间(因为结果是复数)所以下面就可以进行傅里叶变换了
//dft函数(离散傅里叶变换)
//输入输出支持同一个图像
dft(complex,complex);
magnitude = sqrt(Re(DFT)^2 + Im(DFT)^2)。
//现在的complex就是一个傅里叶变换的结果,是一个包含实数部分(Re)和复数部分(imaginary-im),我们要的是幅度公式:
// M = \sqrt[2]{ {Re(DFT(I))}^2 + {Im(DFT(I))}^2}
//转化为openCV代码
split(complex,planes);
magnitude(planes[0], planes[1], planes[0]);
//我们得到了幅度数据
Mat magI = planes[0];
//由于幅度值范围大到不适合在屏幕上显示 数值太大 都是白色,数值就算比较低也可能大于255 所以无法分辨高低 ,所以转化为对数范围
//公式:M_1 = \log{(1 + M)}
magI += Scalar::all(1);
log(magI,magI);
magI = magI(cv::Rect(0,0,magI.cols&-2,magI.rows&-2));
int cx = magI.cols/2;
int cy = magI.rows/2;
Mat q0(magI,cv::Rect(0,0,cx,cy));
Mat q1(magI,cv::Rect(cx,0,cx,cy));
Mat q2(magI,cv::Rect(0,cy,cx,cy));
Mat q3(magI,cv::Rect(cx,cy,cx,cy));
Mat tmp;
q0.copyTo(tmp);
q3.copyTo(q0);
tmp.copyTo(q3);
q1.copyTo(tmp);
q2.copyTo(q1);
tmp.copyTo(q2);
//归一
normalize(magI, magI, 0, 1,CV_MINMAX);
//转化成单通道灰度图(这一步官网没有给出)
Mat magImg(magI.size(),CV_8UC1);
magI.convertTo(magImg, CV_8UC1,255,0);
// 图像的二值化就是将图像上的像素点的灰度值设置为0或255,这样将使整个图像呈现出明显的黑白效果。在数字图像处理中,二值图像占有非常重要的地位,图像的二值化使图像中数据量大为减少,从而能凸显出目标的轮廓。
//CV_THRESH_BINARY参数的作用就是 大于阈值(133) 就是最大值(255) 否则就是0
//有点像LUT 查找表哦 查找表也能实现这个功能
threshold(magImg,magImg,130,255,CV_THRESH_BINARY);
vector lines;
float pi180 = (float)CV_PI/180;
//准备在lineImg画直线
Mat linImg(magImg.size(),CV_8UC3);
//hough变换可以看这个:http://blog.csdn.net/qq_18343569/article/details/48006453
HoughLines(magImg, lines, 1, pi180, 400,0,0);
int numlines = (int)lines.size();
cout << "检测到的直线的个数为 " << numlines << endl;
for(int l = 0; l < numlines; l++){
//现在就是画出检测到的直线
//拿出极径 和 极角
float rho = lines[l][0];
float theta = lines[l][1];
//根据公式 (极坐标下的直线公式)
cv::Point pt1,pt2;
double a= cos(theta);
double b = sin(theta);
double x0 = a*rho;
double y0 = b*rho;
//cvRound对double四舍五入到int类型
pt1.x = cvRound(x0 + 1000*(-b));
pt1.y = cvRound(y0 + 1000*(a));
pt2.x = cvRound(x0 - 1000*(-b));
pt2.y = cvRound(y0 - 1000*(a));
line(linImg,pt1,pt2,Scalar(255,0,0),3,8,0);
}
//实际开发中可能找到 几十条直线(通过调整阈值前提是能看到检测的直线数量,所以很难锁定到一两条直线),其实这些直线无非就一两种斜率,可以算出角度,进行剔除就可以了
//下面我们剔除不要的直线 并且让图旋转回去。
// 由于DFT的特点,只有输入图像是正方形时,检测到的角才是文本真正旋转的角度。但我们的输入图像不一定是正方形的,所以要根据图像的长宽比改变这个角度。
//还有一个需要注意的细节,虽然HoughLines()输出的倾斜角在[0,180)之间,但在[0,90]和(90,180)之间这个角的含义是不同的。
float angel=0;
float piThresh = (float)CV_PI/90;
float pi2 = CV_PI/2;
for(int l=0; l