图像实验5-频域滤波及综合应用实验
一、实验目的
掌握图像进行频域滤波的方法和步骤,掌握视频流的读取和图像帧提取及实时处理,完
成图像兴趣区域的提取。
1、掌握图像频域 DFT 变换和反变换的方法
2、掌握图像频域滤波的步骤
3、能基于 OpenCV 处理视频流
二、实验内容
1、灰度图像的 DFT 和 IDFT
具体内容:利用 OpenCV 提供的 cvDFT 函数对图像进行 DFT 和 IDFT 变换
2、利用理想高通和低通滤波器对灰度图像进行频域滤波
具体内容:利用 cvDFT 函数实现 DFT,在频域上利用理想高通和低通滤波器进行滤波,并把滤波过后的图像显示在屏幕上(观察振铃现象),要求截止频率可输入。
3、利用巴特沃斯高通和低通滤波器对灰度图像进行频域滤波
具体内容:同第 2 步
4、结合所学灰度变换、直方图统计、空域滤波、频域滤波等知识,调研帧间差原理,
并扩展运用特征点、深度学习等方法,对实际的视频样例进行图像帧提取,框选出 字符显示或整个显示屏的区域。
三、实验完成情况
1、灰度图像的 DFT 和 IDFT
核心代码
class FourierTransform {
public:
//图像的傅里叶变换
static FTImage DFT(GrayImage& img) {
Mat src = img.getImage();
//计算用于傅里叶变换的最佳尺寸
int rows = getOptimalDFTSize(src.rows);
int cols = getOptimalDFTSize(src.cols);
Mat dft_src;
//边缘填充方式为用镜像元素填充
int borderType = BORDER_WRAP;
//扩充图像边界为最佳尺寸
copyMakeBorder(src, dft_src, 0, rows - src.rows, 0, cols - src.cols, borderType);
//扩充通道用于存储实部和虚部
vector<Mat> in_vec{ Mat_<float>(src),Mat::zeros(src.size(),CV_32FC1) };
Mat target;
//合并通道
merge(in_vec, target);
//傅里叶变换
dft(target, target);
return FTImage(target);
}
//图像的傅里叶逆变换
static GrayImage IDFT(FTImage& img,string type = "") {
Mat src = img.getImage();
Mat target;
//傅里叶逆变换
idft(src, target);
vector<Mat> output;
split(target, output);
target = output[0];
normalize(target, target, 0, 255, NORM_MINMAX);
convertScaleAbs(target, target);
return GrayImage(target, "逆傅里叶变换 "+type);
}
};
实现截图
2、利用理想高通和低通滤波器对灰度图像进行频域滤波
核心代码
//滤波操作
Mat filterProcess(float kernel(float, float, int), float d, int n) {
int row_ctr = img.rows / 2;
int col_ctr = img.cols / 2;
float dist;
//计算滤波器
Mat filterKernel = Mat::zeros(img.size(), CV_32F);
for (int i = img.rows - 1; i >= 0; i--) {
for (int j = img.cols - 1; j >= 0; j--) {
dist = (float)sqrt(pow(row_ctr - i, 2) + pow(col_ctr - j, 2));
filterKernel.at<float>(i, j) = kernel(dist, d, n);
}
}
FTImage::quadrantExchange(filterKernel);
//图像与滤波器相乘
vector<Mat> vec;
split(img, vec);
for (Mat& m : vec)
multiply(m, filterKernel, m);
Mat target;
merge(vec, target);
return target;
}
//理想低通滤波器计算
static float lowPassKernel(float dist, float d, int n) {
return dist < d ? 1.0f : 0.0f;
}
//理想高通滤波器计算
static float highPassKernel(float dist, float d, int n) {
return dist > d ? 1.0f : 0.0f;
}
实现截图(截止半径输入为50)
3、利用巴特沃斯高通和低通滤波器对灰度图像进行频域滤波
核心代码
//滤波操作函数同上
//巴特沃斯低通滤波器计算
static float lowButterworsePassKernel(float dist, float d, int n) {
return (float)(1.0 / (1.0 + pow(dist / d, 2 * n)));
}
//巴特沃斯高通滤波器计算
static float highButterworsePassKernel(float dist, float d, int n) {
return (float)(1.0 / (1.0 + pow(d / dist, 2 * n)));
}
实现截图(截止半径输入为50,巴特沃兹输入阶数为3)
4、结合所学灰度变换、直方图统计、空域滤波、频域滤波等知识,调研帧间差原理,并扩展运用特征点、深度学习等方法,对实际的视频样例进行图像帧提取,框选出 字符显示或整个显示屏的区域。
核心代码
//逐帧处理
Mat process(Mat& frame, vector<Rect>& rects) {
Mat target;
//转为灰度图像
cvtColor(frame, target, COLOR_RGB2GRAY);
//高斯滤波
GaussianBlur(target, target, Size(3, 3), 0);
//灰度二值化
threshold(target, target, bin_gray_threshold, 255, THRESH_BINARY);
//图像截取
imageCapture(target, radis, delta_x, delta_y);
//边缘检测
Canny(target, target, canny_min, canny_max, sobel_size);
//查找轮廓
vector<vector<Point>>shapes;
findContours(target, shapes, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
//查找矩形框选轮廓,选取最大的矩形
Rect rect, rect_t;
int area = 0, area_t;
for (vector<Point> shape : shapes) {
rect_t = boundingRect(shape);
area_t = rect_t.area();
if (area_min_threshold <= area_t && area_t <= area_max_threshold) {
if (area_t > area) {
area = area_t;
rect = rect_t;
}
}
}
if (area > 0)
rectangle(frame, rect, Scalar(255, 0, 255), 3);
return target;
}
//视频处理播放
void playVideo(string name, int x = 100, int y = 50) {
string full_path = getFullPath(name);
//打开视频文件
VideoCapture video(full_path);
namedWindow(name, WINDOW_AUTOSIZE);
moveWindow(name, x, y);
string test_win_name = "轮廓处理";
namedWindow(test_win_name, WINDOW_AUTOSIZE);
moveWindow(test_win_name, x+500, y);
//逐帧读取
Mat frame;
vector<Rect> rects;
while (1) {
//读取一帧
video >> frame;
if (frame.empty())
break;
imshow(test_win_name, process(frame, rects));
imshow(name, frame);
waitKey(wait_time);
}
}
实现截图
四、实验中的问题
1.对傅里叶变换不够熟悉,理解图像的空域与频率相互转换有些困难。
2.第一次接触对视频进行帧处理,同时还需要综合课上所学的各种知识,由于知识不足因此没能找到更好的处理方案。
3.对于边界提取,图像特征点提取以及帧间差原理等实际应用还有所欠缺,需要深入学习。
五、实验结果
源码
lab5.cpp
#include <opencv2/opencv.hpp>
#include <iostream>
#include <string>
using namespace std;
using namespace cv;
const static std::string path = "F:\\Documents\\高级图像处理\\Image\\";
//图片路径组合
string getFullPath(string name) {
return path + name;
}
//打开图片显示窗口
void openWindows(string win_name, Mat img, int x = 500, int y = 200) {
//窗口命名,指定大小,生成位置
namedWindow(win_name, WINDOW_AUTOSIZE);
moveWindow(win_name, x, y);
//生成窗口显示图片
imshow(win_name, img);
//等待键入
waitKey();
//关闭窗口
destroyWindow(win_name);
}
//统一数字输入函数
template<typename T>T inputNumber(string desc) {
system("cls");
T input;
cout << desc;
cin >> input;
cout << endl;
return input;
}
//统一图片打开函数,用于简化路径和处理打开图片错误
Mat openImage(string name, int type = 1) {
//图片读取函数,返回图像存储类(包含存储方式、存储矩阵、矩阵大小等)
Mat img = imread(getFullPath(name), type);
if (img.empty()) {
cout << "无效图片,读取失败" << endl;
exit(-1);
}
return img;
}
//图像基类
class Image {
protected:
Mat img;
public:
Mat getImage() {
return img.clone();
}
//灰度对数变换,c值默认1
static Mat handleLogarithmic(Mat img, int c = 1) {
//复制图像
Mat src = img.clone();
//图像元素变换为32F浮点类型
src.convertTo(src, CV_32F);
//建立空白目标图像结构
Mat target = Mat::zeros(img.size(), img.type());
//1+r 原图像与标量相加,由于是灰度图像,因此标量为Scalar(1.0),等同于src = src+1
add(src, Scalar(1.0), src);
//log(1+r) 对数变换
log(src, target);
//clog(1+r)
target *= c;
//归一化处理,即将变换后的图像平移、缩放到指定区间(0,255),归一化方式为最小值和最大值范围(NORM_MINMAX)
normalize(target, target, 0, 255, NORM_MINMAX);
//图像增强取绝对值并变换为U8整数类型,此处等同于target.convertTo(target, CV_8U);
convertScaleAbs(target, target);
return target;
}
};
//彩色图像处理类
class ColorImage :public Image {
public:
ColorImage(Mat img, string name) {
this->img = img.clone();
openWindows(name, img);
}
//读取彩色图像并展示
ColorImage(string path) {
img = openImage(path, IMREAD_COLOR);
openWindows("彩色图像", img);
}
};
//灰度图像处理类
class GrayImage :public Image {
public:
GrayImage(Mat img, string name) {
this->img = img.clone();
openWindows(name, img);
}
//仅读取灰度方式读取图像并展示(IMREAD_GRAYSCALE)
GrayImage(string path) {
img = openImage(path, IMREAD_GRAYSCALE);
openWindows("灰度图像", img);
}
};
//傅里叶变换图像处理类
class FTImage :public Image{
private:
//获取幅值图像
static Mat spectrum(Mat ftimg) {
Mat target ;
vector<Mat> mats;
split(ftimg, mats);
//j计算幅值
magnitude(mats[0], mats[1], target);
//交换象限
quadrantExchange(target);
//对数变换并归一化
return handleLogarithmic(target);
}
public:
//交换象限
static void quadrantExchange(Mat& input) {
int cx = input.cols / 2;
int cy = input.rows / 2;
Mat q0(input, Rect(0, 0, cx, cy)); // ROI区域的左上
Mat q1(input, Rect(cx, 0, cx, cy)); // ROI区域的右上
Mat q2(input, Rect(0, cy, cx, cy)); // ROI区域的左下
Mat q3(input, 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);
}
FTImage(Mat img, string name = "傅里叶变换频谱") {
this->img = img.clone();
openWindows(name, spectrum(this->img));
}
};
class FourierTransform {
public:
//图像的傅里叶变换
static FTImage DFT(GrayImage& img) {
Mat src = img.getImage();
//计算用于傅里叶变换的最佳尺寸
int rows = getOptimalDFTSize(src.rows);
int cols = getOptimalDFTSize(src.cols);
Mat dft_src;
//边缘填充方式为用镜像元素填充
int borderType = BORDER_WRAP;
//扩充图像边界为最佳尺寸
copyMakeBorder(src, dft_src, 0, rows - src.rows, 0, cols - src.cols, borderType);
//扩充通道用于存储实部和虚部
vector<Mat> in_vec{ Mat_<float>(src),Mat::zeros(src.size(),CV_32FC1) };
Mat target;
//合并通道
merge(in_vec, target);
//傅里叶变换
dft(target, target);
return FTImage(target);
}
//图像的傅里叶逆变换
static GrayImage IDFT(FTImage& img,string type = "") {
Mat src = img.getImage();
Mat target;
//傅里叶逆变换
idft(src, target);
vector<Mat> output;
split(target, output);
target = output[0];
normalize(target, target, 0, 255, NORM_MINMAX);
convertScaleAbs(target, target);
return GrayImage(target, "傅里叶逆变换 "+type);
}
};
class Filter {
private:
Mat img;
//滤波操作
Mat filterProcess(float kernel(float, float, int), float d, int n) {
int row_ctr = img.rows / 2;
int col_ctr = img.cols / 2;
float dist;
//计算滤波器
Mat filterKernel = Mat::zeros(img.size(), CV_32F);
for (int i = img.rows - 1; i >= 0; i--) {
for (int j = img.cols - 1; j >= 0; j--) {
dist = (float)sqrt(pow(row_ctr - i, 2) + pow(col_ctr - j, 2));
filterKernel.at<float>(i, j) = kernel(dist, d, n);
}
}
FTImage::quadrantExchange(filterKernel);
//图像与滤波器相乘
vector<Mat> vec;
split(img, vec);
for (Mat& m : vec)
multiply(m, filterKernel, m);
Mat target;
merge(vec, target);
return target;
}
//理想低通滤波器计算
static float lowPassKernel(float dist, float d, int n) {
return dist < d ? 1.0f : 0.0f;
}
//理想高通滤波器计算
static float highPassKernel(float dist, float d, int n) {
return dist > d ? 1.0f : 0.0f;
}
//巴特沃斯低通滤波器计算
static float lowButterworsePassKernel(float dist, float d, int n) {
return (float)(1.0 / (1.0 + pow(dist / d, 2 * n)));
}
//巴特沃斯高通滤波器计算
static float highButterworsePassKernel(float dist, float d, int n) {
return (float)(1.0 / (1.0 + pow(d / dist, 2 * n)));
}
public:
Filter(FTImage img) {
this->img = img.getImage();
}
//理想低通滤波
GrayImage handlelowPassKernel(float d) {
string name = "理想低通";
Mat target = filterProcess(lowPassKernel, d, 0);
FTImage ftimg(target, name);
return FourierTransform::IDFT(ftimg, name);
}
//理想高通滤波
GrayImage handlehighPassKernel(float d) {
string name = "理想高通";
Mat target = filterProcess(highPassKernel, d, 0);
FTImage ftimg(target, name);
return FourierTransform::IDFT(ftimg, name);
}
//巴特沃斯低通滤波
GrayImage handlelowButterworsePassKernel(float d, int n) {
string name = "巴特沃斯低通";
Mat target = filterProcess(lowButterworsePassKernel, d, n);
FTImage ftimg(target, name);
return FourierTransform::IDFT(ftimg, name);
}
//巴特沃斯高通滤波
GrayImage handlehighButterworsePassKernel(float d, int n) {
string name = "巴特沃斯高通";
Mat target = filterProcess(highButterworsePassKernel, d, n);
FTImage ftimg(target, name);
return FourierTransform::IDFT(ftimg, name);
}
};
int main() {
string name = "moon.jpg";
//灰度图像
GrayImage gray_img(name);
//傅里叶变换
FTImage ft_img = FourierTransform::DFT(gray_img);
//傅里叶逆变换
GrayImage ift_img = FourierTransform::IDFT(ft_img);
Filter filter(ft_img);
float d = inputNumber<float>("输入截止半径:");
//理想低通滤波
GrayImage lf = filter.handlelowPassKernel(d);
//理想高通滤波
GrayImage hf = filter.handlehighPassKernel(d);
int n = inputNumber<int>("输入阶数:");
//巴特沃斯低通滤波
GrayImage blf = filter.handlelowButterworsePassKernel(d, n);
//巴特沃斯高通滤波
GrayImage hlf = filter.handlehighButterworsePassKernel(d, n);
//彩色图像
//ColorImage color_img(name);
return 0;
}
lab5_ad.cpp
#include <opencv2/opencv.hpp>
#include <iostream>
#include <string>
using namespace std;
using namespace cv;
const static string path = "F:\\Documents\\高级图像处理\\实验\\实验5视频分析对象\\";
const static int wait_time = 1; //每帧间隔时间/ms
const static int canny_min = 150; //轮廓检测最低幅值阀值
const static int canny_max = 200; //轮廓检测最高幅值阀值
const static int sobel_size = 3; //轮廓检测sobel模版大小
const static int bin_gray_threshold = 150; //二值化阀值
const static int area_min_threshold = 900; //矩形最小面积
const static int area_max_threshold = 60000; //矩形最大面积
const static int delta_x = 60; //图像截取x轴偏移
const static int delta_y = 60; //图像截取y轴偏移
const static int radis = 170; //图像截取半径 采用r = max(x, y)计算
//路径组合
string getFullPath(string name) {
return path + name;
}
//判断是否为相近矩形
bool similarRect(vector<Rect> src, Rect pattern) {
return true;
}
//图像截取
void imageCapture(Mat& img, int d, int x = 0, int y = 0) {
x += img.rows / 2;
y += img.cols / 2;
for (int i = img.rows - 1; i >= 0; i--) {
for (int j = img.cols - 1; j >= 0; j--) {
if (max(abs(x - i) , abs(y - j)) > d)
img.at<uchar>(i, j) = 0;
}
}
}
//逐帧处理
Mat process(Mat& frame, vector<Rect>& rects) {
Mat target;
//转为灰度图像
cvtColor(frame, target, COLOR_RGB2GRAY);
//高斯滤波
GaussianBlur(target, target, Size(3, 3), 0);
//灰度二值化
threshold(target, target, bin_gray_threshold, 255, THRESH_BINARY);
//图像截取
imageCapture(target, radis, delta_x, delta_y);
//边缘检测
Canny(target, target, canny_min, canny_max, sobel_size);
//查找轮廓
vector<vector<Point>>shapes;
findContours(target, shapes, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
//查找矩形框选轮廓,选取最大的矩形
Rect rect, rect_t;
int area = 0, area_t;
for (vector<Point> shape : shapes) {
rect_t = boundingRect(shape);
area_t = rect_t.area();
if (area_min_threshold <= area_t && area_t <= area_max_threshold) {
if (area_t > area) {
area = area_t;
rect = rect_t;
}
}
}
if (area > 0)
rectangle(frame, rect, Scalar(255, 0, 255), 3);
return target;
}
//视频处理播放
void playVideo(string name, bool debug = false, int x = 100, int y = 50) {
string full_path = getFullPath(name);
//打开视频文件
VideoCapture video(full_path);
namedWindow(name, WINDOW_AUTOSIZE);
moveWindow(name, x, y);
string test_win_name = "轮廓处理";
if (debug) {
namedWindow(test_win_name, WINDOW_AUTOSIZE);
moveWindow(test_win_name, x + 500, y);
}
//逐帧读取
Mat frame;
vector<Rect> rects;
while (1) {
//读取一帧
video >> frame;
if (frame.empty())
break;
if (debug)
imshow(test_win_name, process(frame, rects));
else
process(frame, rects);
imshow(name, frame);
waitKey(wait_time);
}
}
int main() {
string name = "meter_1.mp4";
playVideo(name, false);
return 0;
}