OpenCV(7)离散傅里叶、图像矫正 C++

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()归一化函数
 **/

执行结果:
在这里插入图片描述
执行结果2:
在这里插入图片描述
在这里插入图片描述

2.图像矫正

#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;
}

执行结果:
在这里插入图片描述
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/soinlove36/article/details/119852974