“毛星云OpenCV3编程入门之python实现”第十篇傅里叶变换

5.5傅里叶变换

python代码:

# -*- coding: utf-8 -*-

import cv2
import numpy as np

# 【1】以灰度模式读取原始图像并显示
srcImage = cv2.imread('E:/Study/python/OpenCV_study/img/9.jpg',0)
cv2.imshow("srcImage: ", srcImage)
print(srcImage.shape)
# 【2】将输入图像延扩到最佳的尺寸,边界用0补充
# 离散傅里叶变换的运行速度与图片的尺寸有很大关系。
# 当图像尺寸是2,3,5整数倍时,计算速度最快。getOptimalDFTSize就是获取最佳尺寸
rows, cols = srcImage.shape[:2]
print("rows: ", rows)
print("cols: ", cols)
newrows = cv2.getOptimalDFTSize(srcImage.shape[0])
newcols = cv2.getOptimalDFTSize(srcImage.shape[1])
print("newrows: ", newrows)
print("newcols: ", newcols)

# 将添加的像素初始化为0.
# copyMakeBorder()函数的作用是扩充图像边界
desImage = cv2.copyMakeBorder(srcImage, 0, newrows-rows, 0, newcols-cols , cv2.BORDER_CONSTANT, value=0)

# 【3】为傅立叶变换的结果(复数)(实部和虚部)分配存储空间。
# 将planes数组组合合并成一个多通道的数组complexI
# 1.傅里叶结果是复数,这就是说对于每个原图像值,结果会有两个图像值
# 2.频域值范围远远超过空间值范围,因此要将频域存储再float格式中。并多加一个通道储存复数部分
print(type(desImage[0][0])) # 强制类型转换前类型为<class 'numpy.uint8'>
desImage = desImage.astype(np.float32)  # 强制类型转换
print(type(desImage[0][0])) # 强制类型转换后类型为<class 'numpy.float32'>
zeros = np.zeros(desImage.shape[:2], dtype=np.float32)  # 新增通道,为傅里叶变换储存复数部分
print(type(zeros[0][0]))# 确认复数数组数据类型 <class 'numpy.float32'>
complexI = cv2.merge((desImage,zeros))
print(complexI.shape)   # 确认comloexI是否已经是两通道矩阵 (576, 480, 2)

# 【4】进行就地离散傅里叶变换
print(complexI[0:2, 0:3, 0])# 先查看通道0的2行,3列数据  [147. 150. 151.] [149. 151. 152.]
print(complexI[0:2, 0:3, 1])# 先查看通道1的2行,3列数据  [0. 0. 0.] [0. 0. 0.]
complexI = cv2.dft(complexI)
print(type(complexI))       # <class 'numpy.ndarray'>
print(complexI.shape)       # (576, 480, 2) 两通道的矩阵
print(complexI[0:2, 0:3, 0])# 检查离散傅里叶变化后的通道0的2行,3列数据 [51342612. -1710898.4 -3318711.8][498098.84 -806181.56 312952.9]
print(complexI[0:2, 0:4, 1])# 检查离散傅里叶变化后的通道1的2行,3列数据 [0. -547902.1 -1145416.9 -2201629.][1348794. 42948.4 79829.984 654627.5]

# 【5】将复数转换为幅值,即=> log(1 + sqrt(Re(DFT(I))^2 + Im(DFT(I))^2))
# 将多通道数组complexI分离成几个单通道数组,planes[0] = Re(DFT(I), planes[1] = Im(DFT(I))
planes0, planes1 = cv2.split(complexI)
cv2.imshow("planes0", planes0)
cv2.imshow("planes1", planes1)
print(type(planes0))          # <class 'numpy.ndarray'>
print(planes0.shape)          # (576, 480)
print(type(planes1))          # <class 'list'>
print(planes1.shape)          # (576, 480)
# 计算二维矢量的幅值
magnitudeImage = cv2.magnitude(planes0, planes1)
cv2.imshow("magnitudeImage1", magnitudeImage)
print(type(magnitudeImage))
print(magnitudeImage.shape)
print(magnitudeImage[0:2, 0:3]) # [51342612.    1796488.2   3510816.  ] [ 1437827.5    807324.75   322974.22]

# 【6】进行对数尺度(logarithmic scale)缩放
# 将复数转换为幅值范围太大。高值显示为白点,而低值为黑点,高低值的变化无法有效分辨。为了在屏幕上凹显出高低变化的连续性,用对数尺度替代线性尺度
magnitudeImage += np.ones(magnitudeImage.shape[:2], dtype=np.float32) # 对幅值都加1,以防原始数据有0的存在。
print(magnitudeImage[0:2, 0:3]) # [51342612.    1796489.2   3510817.  ] [ 1437828.5    807325.75   322975.22]
# 求自然对数
magnitudeImage = cv2.log(magnitudeImage)
print(type(magnitudeImage))     # <class 'numpy.ndarray'>
print(magnitudeImage.shape)     # (576, 480)
print(magnitudeImage[0:2, 0:3]) #[17.754032 14.401345 15.07136 ] [14.178645 13.601482 12.685331]

# 【7】剪切和重分布幅度图象限
# 若有奇数行或奇数列,进行频谱裁剪
# 第二步,为了提高处理速度延扩了图像,现在是剔除添加的像素。
# 为了方便显示,也可以重新分布幅度图像象限位置,
# (注:将第五步得到的幅度图从中间划开,得到4张1/4子图象,将每张子图堪称幅度图的一个象限,重新分布,即将4个角点重叠到图片中心)
# 这样的话原点(0,0)就位移到图像中心了
# magnitudeImage.shape[0] & -2 是要进行位的和运算。
# magnitudeImage.shape[0]和-2需要分别转化为无符整型,再转化为二进制数进行与运算。
# 由于-2是负数,再转换的过程中存在溢出等问题,以8位二进制为例子,-2实际转化为二进制的数为11111110。
# 当一个二进制数与11111110进行位的与运算时,该二进制数的最小数位就为0,转化为无符整型时,就是一个偶数。
# 题主的例子就是利用这个原理获取一个不大于magnitudeImage.shape[0]的偶数。

magnitudeImage = magnitudeImage[0 : magnitudeImage.shape[0] & -2][0 : magnitudeImage.shape[1] & -2]
print(type(magnitudeImage))
print(magnitudeImage.shape)
print(magnitudeImage[0:2, 0:3])
# 重新排列傅立叶图像中的象限,使得原点位于图像中心
cx = int(magnitudeImage.shape[0] / 2)
cy = int(magnitudeImage.shape[1] / 2)
# q0 = magnitudeImage[0:cx, 0:cy]         # ROI区域的左上
# q1 = magnitudeImage[0:cx, cy:cy*2]      # ROI区域的右上
# q2 = magnitudeImage[cx:cx*2, 0:cy]      # ROI区域的左下
# q3 = magnitudeImage[cx:cx*2, cy:cy*2]   # ROI区域的右下
print(magnitudeImage[0:2, 0:3])
# 交换象限(左上与右下进行交换)
tmp = np.copy(magnitudeImage[0:cx, 0:cy])
magnitudeImage[0:cx, 0:cy] = np.copy(magnitudeImage[cx:cx*2, cy:cy*2])
magnitudeImage[cx:cx*2, cy:cy*2] = np.copy(tmp)
# 交换象限(右上与左下进行交换)
tmp = np.copy(magnitudeImage[0:cx, cy:cy*2])
magnitudeImage[0:cx, cy:cy*2] = np.copy(magnitudeImage[cx:cx*2, 0:cy])
magnitudeImage[cx:cx*2, 0:cy] = np.copy(tmp)
print(magnitudeImage[0:2, 0:3])
# 【8】归一化,用0到1之间的浮点值将矩阵变换为可视的图像格式(部分幅度值仍然超过可显示范围[0,1].幅度归一化到可显示范围)
cv2.normalize(magnitudeImage, magnitudeImage, 0, 1, cv2.NORM_MINMAX)
print(magnitudeImage[0:2, 0:3])
# 【9】显示效果图
cv2.imshow("magnitudeImage", magnitudeImage)
cv2.waitKey(0)

原书中的C++代码:

//--------------------------------------【程序说明】-------------------------------------------
//		程序说明:《OpenCV3编程入门》OpenCV2版书本配套示例程序28
//		程序描述:离散傅里叶变换
//		开发测试所用IDE版本:Visual Studio 2010
//		开发测试所用OpenCV版本:	3.0 beta
//		2014年11月 Created by @浅墨_毛星云
//		2014年12月 Revised by @浅墨_毛星云
//------------------------------------------------------------------------------------------------



//---------------------------------【头文件、命名空间包含部分】-----------------------------
//		描述:包含程序所使用的头文件和命名空间
//-------------------------------------------------------------------------------------------------
#include "opencv2/core/core.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <iostream>
using namespace cv;


//-----------------------------------【ShowHelpText( )函数】----------------------------------
//		 描述:输出一些帮助信息
//----------------------------------------------------------------------------------------------
void ShowHelpText()
{
	//输出欢迎信息和OpenCV版本
	printf("\n\n\t\t\t非常感谢购买《OpenCV3编程入门》一书!\n");
	printf("\n\n\t\t\t此为本书OpenCV3版的第28个配套示例程序\n");
	printf("\n\n\t\t\t   当前使用的OpenCV版本为:" CV_VERSION );
	printf("\n\n  ----------------------------------------------------------------------------\n");
}



//--------------------------------------【main( )函数】-----------------------------------------
//          描述:控制台应用程序的入口函数,我们的程序从这里开始执行
//-------------------------------------------------------------------------------------------------
int main( )
{

	//【1】以灰度模式读取原始图像并显示
	Mat srcImage = imread("1.jpg", 0);
	if(!srcImage.data ) { printf("读取图片错误,请确定目录下是否有imread函数指定图片存在~! \n"); return false; } 
	imshow("原始图像" , srcImage);   

	ShowHelpText();

	//【2】将输入图像延扩到最佳的尺寸,边界用0补充
	int m = getOptimalDFTSize( srcImage.rows );
	int n = getOptimalDFTSize( srcImage.cols ); 
	//将添加的像素初始化为0.
	Mat padded;  
	copyMakeBorder(srcImage, padded, 0, m - srcImage.rows, 0, n - srcImage.cols, BORDER_CONSTANT, Scalar::all(0));

	//【3】为傅立叶变换的结果(实部和虚部)分配存储空间。
	//将planes数组组合合并成一个多通道的数组complexI
	Mat planes[] = {Mat_<float>(padded), Mat::zeros(padded.size(), CV_32F)};
	Mat complexI;
	merge(planes, 2, complexI);         

	//【4】进行就地离散傅里叶变换
	dft(complexI, complexI);           

	//【5】将复数转换为幅值,即=> log(1 + sqrt(Re(DFT(I))^2 + Im(DFT(I))^2))
	split(complexI, planes); // 将多通道数组complexI分离成几个单通道数组,planes[0] = Re(DFT(I), planes[1] = Im(DFT(I))
	magnitude(planes[0], planes[1], planes[0]);// planes[0] = magnitude  
	Mat magnitudeImage = planes[0];

	//【6】进行对数尺度(logarithmic scale)缩放
	magnitudeImage += Scalar::all(1);
	log(magnitudeImage, magnitudeImage);//求自然对数

	//【7】剪切和重分布幅度图象限
	//若有奇数行或奇数列,进行频谱裁剪      
	magnitudeImage = magnitudeImage(Rect(0, 0, magnitudeImage.cols & -2, magnitudeImage.rows & -2));
	//重新排列傅立叶图像中的象限,使得原点位于图像中心  
	int cx = magnitudeImage.cols/2;
	int cy = magnitudeImage.rows/2;
	Mat q0(magnitudeImage, Rect(0, 0, cx, cy));   // ROI区域的左上
	Mat q1(magnitudeImage, Rect(cx, 0, cx, cy));  // ROI区域的右上
	Mat q2(magnitudeImage, Rect(0, cy, cx, cy));  // ROI区域的左下
	Mat q3(magnitudeImage, Rect(cx, cy, cx, cy)); // ROI区域的右下
	//交换象限(左上与右下进行交换)
	Mat tmp;                           
	q0.copyTo(tmp);
	q3.copyTo(q0);
	tmp.copyTo(q3);
	//交换象限(右上与左下进行交换)
	q1.copyTo(tmp);                 
	q2.copyTo(q1);
	tmp.copyTo(q2);

	//【8】归一化,用0到1之间的浮点值将矩阵变换为可视的图像格式
	//此句代码的OpenCV2版为:
	//normalize(magnitudeImage, magnitudeImage, 0, 1, CV_MINMAX); 
	//此句代码的OpenCV3版为:
	normalize(magnitudeImage, magnitudeImage, 0, 1, NORM_MINMAX); 

	//【9】显示效果图
	imshow("频谱幅值", magnitudeImage);    
	waitKey();

	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值