画像処理におけるフーリエ変換の応用

基本的

離散フーリエ変換 (離散フーリエ再変換、DFT と略記) は、フーリエ変換が時間領域と周波数領域の両方で離散形式を表現することを意味します。簡単に言うと、画像に対してフーリエ変換を行うことは、画像をsinとcosに分解すること、つまり、画像を空間領域から周波数領域に変換することです。図に示すように、変換にはオイラーの公式を使用できます。
ここに画像の説明を挿入
変換結果 F(u,v) は、実数部と虚数部を含む複素数です。
ここに画像の説明を挿入
等級画像には、必要なほぼすべての幾何学的情報が含まれています。一般的に必要なのは、次のとおりです。振幅画像を使用します。フーリエ変換によって元の画像を変更する場合は、最初に強度画像を変更し、次に強度画像と位相画像を逆変換して結果画像を取得します。
ここに画像の説明を挿入
ただし、フーリエ係数のダイナミック レンジが大きすぎて画面に表示できません。グレースケール値を使用して視覚化する場合、線形スケール上の振幅プロットを対数スケールに変換できます:
M1=log(1+M)

opencvのフーリエ変換のAPI:

void dft(InputArray src, OutputArray dst, int flags=0, int nonzeroRows=0); 

式 (1) から次のことが得られます。
ここに画像の説明を挿入

したがって、dft() 関数を使用して逆フーリエ変換を実行することもできます。

画像処理への応用

フーリエ変換は画像処理において多くの機能を持っています。フーリエ解析には画像処理の多くの側面が含まれるだけでなく、離散コサイン変換、ガボール、ウェーブレットなどの改良されたフーリエ アルゴリズムにも画像処理における重要なコンポーネントがあるためです。フーリエ変換は次のような側面で適用されます。
ここに画像の説明を挿入
以下は、画像の回転方向へのフーリエ変換の適用の実装です。4 つのステップに分かれています。
(1) 元の画像に対して DFT 変換を実行し、複素数 F( u ,v);
(2) 実数部と虚数部から光度マップを合成し、M1=log(1+M) 変換を実行する; (3)
原点が になるように光度マップの 4 象限を並べ替える画像の中心、高周波は隅にあり、振幅は[0, 255]に変換されます
(4) ハフ変換を使用して直線を見つけ、回転角度を計算し、画像を回転します

実装コードは次のとおりです。

#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <iostream>
#include <ctime>
#include "math.h"

using namespace cv;
using namespace std;

int main(int argc, char** argv)
{
    
    
	//以单通道(灰度)方式读取图像
	const char* filename = "../data/blue.jpg";
	Mat srcImg = imread(filename, CV_LOAD_IMAGE_GRAYSCALE);
	if (srcImg.empty())
		return -1;

	Point center(srcImg.cols / 2, srcImg.rows / 2);

	clock_t start = clock();
	//将图像扩展到最佳大小,以获得更快的处理速度
	//设置4个方向的边框宽度
	//如果 borderType==BORDER_CONSTANT, 用(0,0,0)填充
	Mat padded;
	int opWidth = getOptimalDFTSize(srcImg.rows);
	int opHeight = getOptimalDFTSize(srcImg.cols);
	copyMakeBorder(srcImg, padded, 0, opWidth - srcImg.rows, 0, opHeight - srcImg.cols, BORDER_CONSTANT, Scalar::all(0));

	Mat planes[] = {
    
     Mat_<float>(padded), Mat::zeros(padded.size(), CV_32F) };
	Mat comImg;
	//合并成一个双通道图像
	merge(planes, 2, comImg);

	//傅里叶变换
	dft(comImg, comImg);

	//计算幅值
	//复数形式:planes[0]=Re(DFT(I)), planes[1]=Im(DFT(I)), magnitude=sqrt(Re^2+Im^2)
	split(comImg, planes);
	magnitude(planes[0], planes[1], planes[0]);

	//转换到log的尺度下,从而能观察到图像(否则数据过界)
	//M2=log(1+M1)
	Mat magMat = planes[0];
	magMat += Scalar::all(1);
	log(magMat, magMat);

	//剪切和重分布幅度图象限

	//若有奇数行或奇数列,进行裁剪
	magMat = magMat(Rect(0, 0, magMat.cols & -2, magMat.rows & -2));

	//重新排布傅里叶图像的4个象限,使原点位于图像中心,高频在角落
	int cx = magMat.cols / 2;
	int cy = magMat.rows / 2;

	Mat q0(magMat, Rect(0, 0, cx, cy));
	Mat q1(magMat, Rect(0, cy, cx, cy));
	Mat q2(magMat, Rect(cx, cy, cx, cy));
	Mat q3(magMat, Rect(cx, 0, cx, cy));

	//交换象限
	Mat tmp;
	q0.copyTo(tmp);
	q2.copyTo(q0);
	tmp.copyTo(q2);

	q1.copyTo(tmp);
	q3.copyTo(q1);
	tmp.copyTo(q3);

	//将幅度标准化至 [0,1], 再转为[0,255]
	normalize(magMat, magMat, 0, 1, CV_MINMAX);
	Mat magImg(magMat.size(), CV_8UC1);
	magMat.convertTo(magImg, CV_8UC1, 255, 0);   // 像素值*255+0

	//以下部分是对频谱图像进行霍夫直线变换,找到角度值,利用该角度值对原图像进行旋转,多适用于文字图像

	//转为二值图像
	threshold(magImg, magImg, 150, 255, CV_THRESH_BINARY);

	//旋转原图
	//使用霍夫变换查找直线
	vector<Vec2f> lines;
	float pi180 = (float)CV_PI / 180;
	Mat linImg(magImg.size(), CV_8UC3);
	HoughLines(magImg, lines, 1, pi180, 500, 0, 0);
	int numLines = lines.size();
	for (int l = 0; l < numLines; l++)
	{
    
    
		float rho = lines[l][0], theta = lines[l][1];
		Point pt1, pt2;
		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));
		line(linImg, pt1, pt2, Scalar(255, 0, 0), 1, 8, 0);
	}

	//imwrite("imageText_line.jpg",linImg);
	if (lines.size() == 3) {
    
    
		cout << "found three angels:" << endl;
		cout << lines[0][1] * 180 / CV_PI << endl << lines[1][1] * 180 / CV_PI << endl << lines[2][1] * 180 / CV_PI << endl << endl;
	}

	//Find the proper angel from the three found angels
	float angel = 0;
	float piThresh = (float)CV_PI / 90;
	float pi2 = CV_PI / 2;
	for (int l = 0; l < numLines; l++)
	{
    
    
		float theta = lines[l][1];
		if (abs(theta) < piThresh || abs(theta - pi2) < piThresh)
			continue;
		else {
    
    
			angel = theta;
			break;
		}
	}

	//计算旋转角度
	//如果图像不是方的,旋转后可能会达不到预期效果
	angel = angel < pi2 ? angel : angel - CV_PI;
	if (angel != pi2) {
    
    
		float angelT = srcImg.rows * tan(angel) / srcImg.cols;
		angel = atan(angelT);
	}
	float angelD = angel * 180 / (float)CV_PI;
	cout << "the rotation angel to be applied:" << endl << angelD << endl << endl;

	clock_t end = clock();
	cout << "花费了" << (double)(end - start) / CLOCKS_PER_SEC << "秒" << endl;

	//旋转图像
	Mat rotMat = getRotationMatrix2D(center, angelD, 1.0);
	Mat dstImg = Mat::ones(srcImg.size(), CV_8UC3);
	warpAffine(srcImg, dstImg, rotMat, srcImg.size(), 1, 0, Scalar(0, 0, 0));

	return 0;
}

実装結果は以下の図に示されています。
ここに画像の説明を挿入
この記事では画像回転方向のフーリエ変換の適用のみを実装しており、将来的に他のいくつかの分野で例を実装する予定です。コード内での関連関数の使用に関して、画像回転の実装についてはあまり研究が行われていません。必要に応じて、今後の紹介のためにこのブログ投稿を改訂します。

参考文献:
OpenCV チュートリアル
CSDN ブログ post_01
CSDN ブログ post_02
非常に詳細な B ステーション チュートリアル

おすすめ

転載: blog.csdn.net/weixin_38584764/article/details/128136123