图像实验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;
}

posted @ 2021-06-30 13:11  wsxuehao  阅读(1383)  评论(0)    收藏  举报