文章目录
1、傅里叶变换
时域分析:以时间作为参照物,世间万物都是随着时间变化而变化,并且不会停止
频域分析:认为世间万物都是静止的,永恒不变的
通过以下制作饮料的过程可以很好的理解傅里叶变换。
1、从时域分析:就是六点零一放了1块冰糖,3颗红豆,2颗绿豆,4块西红柿,1杯纯净水,六点零二放了1块冰糖。。。。随着时间的变化一直在变化
2、从频域角度分析:不在是以时间为参照物了,而是这个事情的频率,1分钟放1块冰糖,2分钟放3粒红豆,3分钟放2粒绿豆,4分钟放4块西红柿,5分钟放1杯水。
下面这两个图都是描述同一个事情,可以更明显看出,两者的区别。
在两个角度去看周期函数的变化
任何连续的周期信号,可以由一组适当的正𫠊曲线组成
相为:三个开始起点不一致的余𫠊函数,组成了这个曲线
2、 CV 傅里叶
1.离散傅里叶变换
作用: 得到图像中集合结构信息
结论:傅里叶变换后的白色部分(即幅度大的低频部分)表示图像中快变化的特性或者说是灰度变化缓慢的特性(低频部分)。
傅里叶变换后的黑恶部分(即幅度低的高频部分),标识图像中快变化的特性,或者说是灰度变化快的特性(高频部分)。
1.1.(cv :: dft)离散傅里叶变换
void dft(InputArray src, OutputArray dst, int flage=0, int nonzeroRow=0)
src:输入矩阵,可以为实数或者虚数。
dst:函数调用后的运算结果存在这里,其尺寸取决于标识符,也就是第三个参数。
falgs:转换的标识符,有默认值0,取值可以为表中的结合
1.2.标识符名称 | 意义
标识符名称 | 意义 |
---|---|
DFT_INVERSE | 用一维或二维逆变换代替默认的正向变换。 |
DFT_SCALE | 缩放比例标识符,输出的结果都会以1/N进行放缩,通常擦很难过会结合DFT_INVERSE一起使用。 |
DFT_ROWS | 对输入矩阵的每行进行正向或反向的变换,此标识符可以在处理多种矢量的时候用于减小资源的开销,这些处理常常是三维或高位变换等复杂操作 |
DFT_COMPLEX_OUTPUT | 进行一维或二维复数苏胡祖反变换。这样的结果通常是一个大小相同的复矩阵。如果输入的矩阵有复数的共轭对称性(比如是一个带有DEF_COMPLEX_OUTPUT标识符的正变换结果),便会输出实矩阵。 |
int 类型的nonzeroRows | 有默认值0.当此参数设为非零时(最好是取值为想要处理的那一行的值,比如C。rows),函数会假设只有输入矩阵的第一个非零行包含非零元素(没有设置DFT_INVERSE标识符),或只有输出矩阵的一个非零行包含非零元素(设置了DFT_INVERSE标识符)。这样的话,函数就可对其他行进行更高效的处理,以节省时间开销。 |
1.3、(cv :: getOptimalDFTSize)返回DFT最优尺寸大小
int getOptimalDFTSize(int vecsize)
vecsize:向量尺寸,即图片的rows、cols。
1.4.(cv :: copyMakeBorder)扩充图像边界
void copyMakeBorder(InputArray src, OutputArray dst, int top, int bottom, int left, int right, int borderType, const Scalar& value=Scalar())
src:输入图像,即源图像,填Mat类型的对象即可。
dst:函数调用后的运算结果存在这里,即这个参数用于存放函数调用后的输出结果,需和源图片有一样的尺寸和类型,且size 应该为Size(src.cols+left+right , src.rows+top+bottom)。
top、bottom、left、right:分别表示在源图像的四个方向上填充多少像素。
borderType:边界类型,常见取值为BORDER_CONSTANT,可参考borderInterpolate()得到更多细节。 value:有默认值Scalar(),可以理解为默认值为0。当borderType取值为BORDER_CONSTANT时,这个参数表示边界值。
1.5(cv :: magnitude)计算二维矢量的幅值
void magnitude(InputArray x, InputArray y, OutputArray magnitude)
x:表示矢量的浮点型X坐标值,也就是实部。
y:表示矢量的浮点型Y坐标值,也就是虚部。
magnitude:输出的幅值,它和第一个参数x有着同样的尺寸和类型。
1.6.(cv :: log)计算数组元素绝对值的自然对数
void log(InputArray src, OutputArray dst)
src:输入图像
dst:得到的对数值
1.7.(cv :: normalize)矩阵归一化
void normalize(InputArray src, OutputArray dst, double alpha=1, double beta=0, int norm_type=NORM_L2, int dtype=-1, InputArray mask=noArray())
src:输入图像,即源图像,填Mat类的对象即可。
dst:函数调用后的运算结果。和源图片有一样的尺寸和类型。
alpha:归一化后的最大值,默认值1。
beta:归一化后的最小值,默认值0。
norm_type:归一化类型,有NORM_INF、NORM_L1、NORM_L2和NORM_MINMAX等参数可选,有默认值NORM_12。
dtype:有默认值-1。当参数去负值时,输出矩阵和src有同样的类型,否则,它和src有同样的通道数,且此时图像深度为CV_MAT_DEPTH
(dtype)。
mask:可选的操作掩膜,有默认值noArray()。
1.8(cv :: copyMakeBorder)扩充图像边界
CV_EXPORTS_W void copyMakeBorder(InputArray src, OutputArray dst,
int top, int bottom, int left, int right,
int borderType, const Scalar& value = Scalar() );
src:源图像。
dst:输出图像,和原图像有一样的深度,size = Size(src.cols + left +right, src.rows + top + bottom);
top,bottom,left,right:分别表示在原图像的四个方向上扩充多少像素。
borderType:边界类型。
1. BORDER_REPLICATE:复制法,复制最边缘像素,填充扩充的边界。中值滤波就采用这种方法。 aaaaaa | abcdefgh
|hhhhhhh
2. BORDER_REFLECT_101:对称法,以最边缘像素为轴,对称填充。filter2D, blur, GaussianBlur,
bilateralFilter 边界处理的默认方法。 gfedcb | abcdefgh | gfedcba
3. BORDER_CONSTANT:以一个常量像素值【参数 value 】填充扩充的边界。这种方式在仿射变换,透视变换中非常常见。
iiiiii | abcdefgh | iiiiiii
4. BORDER_REFLECT: 和对称法原理一致,不过连最边缘像素也要对称过去。 fedcba | abcdefgh | hgfedcb
5. BORDER_WRAP:用另一侧元素来填充这一侧的扩充边界。 cdefgh | abcdefgh | abcdefg
value:默认值为 0,当 borderType 取值为 BORDER_CONSTANT 时,这个参数表示边界值。
1.9.(cv :: merge)将多个数组合并成一个多通道的数组
void merge(const Mat& mv, size_tcount, OutputArray dst);
void merge(InputArrayOfArray mv, OutputArray dst);
mv:填需要被合并的输入矩阵或vector容器的阵列,这个mv参数中所有的矩阵必须有着一样的尺寸和深度。
size_tcount:当mv为一个空白的C数组时,代表输入矩阵的个数,必须大于1。
dst:输出矩阵,和mv[0]拥有一样的尺寸和深度,并且通道的数量是矩阵阵列中的通道的总数。
示例代码
//discrete fourier tranform, 离散傅里叶变换
//头文件
#include "opencv2/core.hpp" //Core functionality,核心函数相关
#include "opencv2/imgproc.hpp" //Image processing, 图像处理相关
#include "opencv2/imgcodecs.hpp"//Image file reading and writing, 图像的加载和写出相关
#include "opencv2/highgui.hpp" //High-level GUI,图形界面GUI相关
#include <iostream>
/**
* 程序流程
* 1、加载图像,格式为灰度图
* 2、获取图片dft变换的最佳大小
* 3、边框加0的方式填充图片,即非0部分为dft变换的最佳大小
* 4、创建数组储存图像实部虚部,且合并到complexI
* 5、傅里叶变换 dft(complexI, complexI)
* 6、重新分离实部虚部,并且计算幅度
* 7、将幅度映射到对数域
* 8、以图像中心为原点划分象限,每个象限创建一个ROI
* 9、对角象限互换
* 10、显示结果
*/
//命名空间
using namespace cv;
using namespace std;
//帮助函数,输出程序的信息
static void help(void)
{
cout << endl
<< "This program demonstrated the use of the discrete Fourier transform (DFT). " << endl //离散傅里叶变换示例
<< "The dft of an image is taken and it's power spectrum is displayed." << endl //离散傅里叶变换后显示功率谱
<< "Usage:" << endl
<< "./discrete_fourier_transform [image_name -- default ../data/lena.jpg]" << endl; //默认加载图片路径
}
int main(int argc, char** argv)
{
help();
//获取图像路径(文件名),命令行输入否则默认
const char* filename = argc >= 2 ? argv[1] : "../data/test3.jpg";
//加载图像,方式为加载灰度图
Mat I = imread(filename, IMREAD_GRAYSCALE);
//检查是否成功加载
if (I.empty()) {
cout << "Error opening image" << endl;
return -1;
}
//! [expand]
Mat padded;
//expand input image to optimal size, 将输入图像扩展到最佳大小
int m = getOptimalDFTSize(I.rows);
int n = getOptimalDFTSize(I.cols);
// on the border add zero values,在边框上添加零值,使用copyMakeBorder函数
copyMakeBorder(I, padded, 0, m - I.rows, 0, n - I.cols, BORDER_CONSTANT, Scalar::all(0));
//! [expand]
//! [complex_and_real] 实部和虚部
Mat planes[] = { Mat_<float>(padded), Mat::zeros(padded.size(), CV_32F) }; //Mat 数组储存图像的实部和虚部
Mat complexI;
merge(planes, 2, complexI); // Add to the expanded another plane with zeros,用零添加到扩展的另一平面
//! [complex_and_real]
//! [dft]
//离散傅里叶变换
dft(complexI, complexI); // this way the result may fit in the source matrix,这种方式的结果可能适合在源矩阵
//! [dft]
// compute the magnitude and switch to logarithmic scale,计算幅度并映射到对数刻度
//公式 => log(1 + sqrt(Re(DFT(I))^2 + Im(DFT(I))^2))
//! [magnitude] 幅度
split(complexI, planes); // planes[0] = Re(DFT(I), planes[1] = Im(DFT(I)),分离实部和虚部
magnitude(planes[0], planes[1], planes[0]);// planes[0] = magnitude,计算幅度且存放到planes[0]
Mat magI = planes[0]; //幅度
//! [magnitude]
//! [log]
magI += Scalar::all(1); // switch to logarithmic scale,映射到对数刻度
log(magI, magI);
//! [log]
//! [crop_rearrange]裁剪重新排列
// crop the spectrum, if it has an odd number of rows or columns, 裁剪频谱, 如果它有奇数行或列数
magI = magI(Rect(0, 0, magI.cols & -2, magI.rows & -2));
// rearrange the quadrants of Fourier image so that the origin is at the image center
//重新排列傅立叶图像的象限, 使原点位于图像中心
int cx = magI.cols / 2;
int cy = magI.rows / 2;
//每个象限新建一个ROI
Mat q0(magI, Rect(0, 0, cx, cy)); // Top-Left - Create a ROI per quadrant, 左上,第二象限
Mat q1(magI, Rect(cx, 0, cx, cy)); // Top-Right, 右上,第一象限
Mat q2(magI, Rect(0, cy, cx, cy)); // Bottom-Left,左下,第三象限
Mat q3(magI, Rect(cx, cy, cx, cy)); // Bottom-Right, 右下,第四象限
Mat tmp; // swap quadrants (Top-Left with Bottom-Right),交换左上和右下象限
q0.copyTo(tmp);
q3.copyTo(q0);
tmp.copyTo(q3);
q1.copyTo(tmp); // swap quadrant (Top-Right with Bottom-Left),交换右上和左下象限
q2.copyTo(q1);
tmp.copyTo(q2);
//! [crop_rearrange]
//! [normalize]
//归一化,像素值都映射到[0,1]之间
normalize(magI, magI, 0, 1, NORM_MINMAX); // Transform the matrix with float values into a
// viewable image form (float between values 0 and 1).
//! [normalize]
//显示结果
imshow("Input Image", I); // Show the result
imshow("spectrum magnitude", magI);
waitKey();
return 0;
}
/**
* 要点总结:
* 加载图片格式为灰度图
* getOptimalDFTSize()函数获取最佳大小
* copyMakeBorder()加框函数
* 实部虚部
* merge()合并函数
* dft()函数
* 幅度公式sqrt(Re(DFT(I))^2 + Im(DFT(I))^2)
* split()分离函数
* magnitude()计算幅度
* log()对数函数
* normalize()归一化函数
**/
图片矫正示例
#include "opencv2/imgproc.hpp"
#include "opencv2/highgui.hpp"
#include <iostream>
using namespace cv;
using namespace std;
#define ERROR 1234
//度数转换
double DegreeTrans(double theta)
{
double res = theta / CV_PI * 180;
return res;
}
//逆时针旋转图像degree角度(原尺寸)
void rotateImage(Mat src, Mat& img_rotate, double degree)
{
//旋转中心为图像中心
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);
//计算二维旋转的仿射变换矩阵
Mat M = getRotationMatrix2D(center, degree, 1);
warpAffine(src, img_rotate, M, Size(length, length), 1, 0, Scalar(255, 255, 255));//仿射变换,背景色填充为白色
}
//通过霍夫变换计算角度
double CalcDegree(const Mat& srcImage, Mat& dst)
{
Mat midImage, dstImage;
Canny(srcImage, midImage, 50, 200, 3);
cvtColor(midImage, dstImage, COLOR_GRAY2BGR);
//通过霍夫变换检测直线
vector<Vec2f> lines;
HoughLines(midImage, lines, 1, CV_PI / 180, 300, 0, 0);//第5个参数就是阈值,阈值越大,检测精度越高
//cout << lines.size() << endl;
//由于图像不同,阈值不好设定,因为阈值设定过高导致无法检测直线,阈值过低直线太多,速度很慢
//所以根据阈值由大到小设置了三个阈值,如果经过大量试验后,可以固定一个适合的阈值。
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 << "没有检测到直线!" << 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));
//只选角度最小的作为旋转角度
sum += theta;
line(dstImage, pt1, pt2, Scalar(55, 100, 195), 1, LINE_AA); //Scalar函数用于调节线段颜色
imshow("直线探测效果图", dstImage);
}
float average = sum / lines.size(); //对所有角度求平均,这样做旋转效果会更好
cout << "average theta:" << average << endl;
double angle = DegreeTrans(average) - 90;
rotateImage(dstImage, dst, angle);
//imshow("直线探测效果图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("旋转调整后", dst);
Mat resulyImage = dst(Rect(0, 0, dst.cols, 500)); //根据先验知识,估计好文本的长宽,再裁剪下来
imshow("裁剪之后", resulyImage);
imwrite("recified.jpg", resulyImage);
}
int main()
{
ImageRecify("./image/test9.jpg", "./image/test9_1.jpg");
waitKey();
return 0;
}