图像卷积操作的手动实现(基于opencv的C++编译环境)

        opencv环境下有自带的filter2D()函数可以实现图像的卷积,自己写一个卷积函数函数貌似是没事找事。。。。好吧,事实是这是我们计算机视觉课程上的一项作业。我们很多算法过程仅仅只调用别人写好的接口,即使原理我们已经清楚,但是真正编写代码的时候很多细节我们可能还是没有意识到,也许自己再实现一遍是一种深入学习的途径吧。

本文对图像卷积操作的原理不作详细讨论,博客https://blog.csdn.net/chaipp0607/article/details/72236892?locationNum=9&fps=1已经写得非常详细,所以这里只对opencv环境下的C++实现进行讨论。

OK,先上代码为敬

这里定义了一个My_Convolution的类,以下是头文件和源文件

#ifndef MY_CONVOLUTION
#define MY_CONVOLUTION

#include <opencv2/opencv.hpp>

class My_Convolution{
public:
    My_Convolution();
    ~My_Convolution();
    bool load_kernal(cv::Mat kernal);//加载卷积核
    void convolute(const cv::Mat &image,cv::Mat &dst);//卷积操作

private:
    bool kernal_loaded;//是否已经加载卷积核
    cv::Mat curr_kernal;//当前卷积核
    int bios_x,bios_y;//记录偏移量

    void compute_sum_of_product(int i,int j,int chan,cv::Mat &complete_image,cv::Mat & dst);//计算每一个像素的掩模乘积之和
    void complete_image_transform(const cv::Mat &image,cv::Mat &dst);//将原图像转换成边框补全的图像
};

#endif // MY_CONVOLUTION


#include "my_convolution.h"

using namespace std;
using namespace cv;

My_Convolution::My_Convolution(){
    kernal_loaded=false;
}
My_Convolution::~My_Convolution(){}

//加载卷积核
bool My_Convolution::load_kernal(Mat kernal){
    if(kernal.cols%2==1&&kernal.rows%2==1){
        curr_kernal=kernal.clone();
        bios_x=(kernal.cols-1)/2;
        bios_y=(kernal.rows-1)/2;
        kernal_loaded=true;
        return true;
    }
    else{
        cout<<"The size of kernal is not suitable!"<<endl;
        return false;
    }
}

//卷积操作
void My_Convolution::convolute(const Mat &image, Mat &dst){
    if(!kernal_loaded){
        cout<<"kernal is empty!Please load the kernal first!"<<endl;return;
    }
    Mat complete_image;
    complete_image_transform(image,complete_image);
    dst=Mat::zeros(image.rows,image.cols,image.type());
    int channels=image.channels();//获取图像的通道数
    if(channels==3){
        for(int chan=0;chan<channels;chan++){
            for(int i=0;i<dst.rows;i++){
                for(int j=0;j<dst.cols;j++){
                    compute_sum_of_product(i,j,chan,complete_image,dst);
                }
            }
        }
        return ;
    }
    if(channels==1){
        for(int i=0;i<dst.rows;i++){
            for(int j=0;j<dst.cols;j++){
                compute_sum_of_product(i,j,0,complete_image,dst);
            }
        }
    }

}

//计算掩模乘积之和
void My_Convolution::compute_sum_of_product(int i, int j,int chan,Mat &complete_image, Mat &dst){
    if(complete_image.channels()==3){
        float sum=0;
        int bios_rows=i;
        int bios_cols=j;
        for(int curr_rows=0;curr_rows<curr_kernal.rows;curr_rows++){
            for(int curr_cols=0;curr_cols<curr_kernal.cols;curr_cols++){
                float a=curr_kernal.at<float>(curr_rows,curr_cols)*complete_image.at<Vec3b>(curr_rows+bios_rows,curr_cols+bios_cols)[chan];
                sum+=a;
            }
        }
        dst.at<Vec3b>(i,j)[chan]=(int)sum;
    }
    else{
        if(complete_image.channels()==1){
            float sum=0;
            int bios_rows=i;
            int bios_cols=j;
            for(int curr_rows=0;curr_rows<curr_kernal.rows;curr_rows++){
                for(int curr_cols=0;curr_cols<curr_kernal.cols;curr_cols++){
                    float a=curr_kernal.at<float>(curr_rows,curr_cols)*complete_image.at<uchar>(curr_rows+bios_rows,curr_cols+bios_cols);
                    sum+=a;
                }
            }
            dst.at<uchar>(i,j)=(int)sum;
        }
        else{
            cout<<"the type of image is not suitable!"<<endl;return ;
        }
    }


}

//边框像素补全
void My_Convolution::complete_image_transform(const Mat &image,Mat &dst){
    if(!kernal_loaded){
        cout<<"kernal is empty!"<<endl;
        return ;
    }
    dst=Mat::zeros(2*bios_y+image.rows,2*bios_x+image.cols,image.type());//初始化一个补全图像的大小。
    Rect real_roi_of_image=Rect(bios_x,bios_y,image.cols,image.rows);
    Mat real_mat_of_image=dst(real_roi_of_image);
    image.copyTo(dst(real_roi_of_image));
}

        My_Convolution类对外提供了load_kernal()和convolute()两个函数接口,分别用来加载卷积核和进行卷机操作。若在convolute操作之前没有加载卷积核,则会给出提醒。    

        当卷积核以边缘像素为基准点进行卷积操作的时候,会出现卷积核内部分的值无法在原图像上找到相互对应的像素点,因为原图必然有边界。这里采取的办法是,在加载卷积核之后,获取卷积核的尺寸,根据卷积核的尺寸适当地在原图像的边缘增加bios_x列和bios_y行的像素,因此补全之后带有原图信息的新的图像的尺寸为(2*bios_x+image.cols)*(2*bios_y+image.rows)。

        得到补全的图像之后,使用一个2层的for循环来对原图像进行卷积操作,新产生的图像的尺寸要和原图像保持一致。

main函数里的测试代码:

#include <iostream>
#include <opencv2/opencv.hpp>
#include "my_convolution.h"

using namespace std;
using namespace cv;

//高斯核构造函数
Mat Gaussian_kernal(int kernal_size, int sigma)
{
    const double PI = 3.14159265358979323846;
    int m = kernal_size / 2;
    Mat kernal(kernal_size, kernal_size, CV_32FC1);
    float s = 2 * sigma*sigma;
    for (int i = 0; i < kernal_size; i++)
    {
        for (int j = 0; j < kernal_size; j++)
        {
            int x = i - m, y=j - m;
            kernal.ptr<float>(i)[j] = exp(-(x*x + y*y) / s) / (PI*s);
        }
    }
    return kernal;
}

//3*3均值卷积核
cv::Mat average_kernal_3 = (Mat_<float>(3,3) << 0.111, 0.111 ,0.111,
                                                0.111, 0.111, 0.111,
                                                0.111, 0.111, 0.111);

//5*5均值卷积核
cv::Mat average_kernal_5 = (Mat_<float>(3,3) << 0.04, 0.04 ,0.04, 0.04, 0.04,
                                                0.04, 0.04 ,0.04, 0.04, 0.04,
                                                0.04, 0.04 ,0.04, 0.04, 0.04,
                                                0.04, 0.04 ,0.04, 0.04, 0.04,
                                                0.04, 0.04 ,0.04, 0.04, 0.04);
//sobel边缘检测算子
cv::Mat sobel_y_kernal= (Mat_<float>(3,3) << -1, -2 ,-1,
                                              0,  0 , 0,
                                              1,  2 , 1);
cv::Mat sobel_x_kernal= (Mat_<float>(3,3) << -1,  0 , 1,
                                             -2,  0 , 2,
                                             -1,  0 , 1);

//prewitt边缘检测算子
cv::Mat prewitt_y_kernal= (Mat_<float>(3,3) << -1, -1 ,-1,
                                                0,  0 , 0,
                                                1,  1 , 1);
cv::Mat prewitt_x_kernal= (Mat_<float>(3,3) << -1,  0 , 1,
                                               -1,  0 , 1,
                                               -1,  0 , 1);

int main(){
    My_Convolution myconvolution;   
    Mat image=imread("lena.jpg");
    imshow("src",image);

    Mat dst_prewitt;
    //高斯卷积
    Mat dst_gaussian;
    myconvolution.load_kernal(Gaussian_kernal(7,2));
    myconvolution.convolute(image,dst_gaussian);

    imshow("dst_gaussian",dst_gaussian);
    //均值3*3
    Mat dst_aver_3;
    myconvolution.load_kernal(average_kernal_3);
    myconvolution.convolute(image,dst_aver_3);
    imshow("dst_aver_3",dst_aver_3);
    //均值5*5
    Mat dst_aver_5;
    myconvolution.load_kernal(average_kernal_5);
    myconvolution.convolute(image,dst_aver_5);
    imshow("dst_aver_5",dst_aver_5);
    //sobel操作
    Mat dst_sobel_x;
    Mat dst_sobel_y;

    myconvolution.load_kernal(sobel_x_kernal);
    myconvolution.convolute(image,dst_sobel_x);
    imshow("dst_sobel_x",dst_sobel_x);

    myconvolution.load_kernal(sobel_y_kernal);
    myconvolution.convolute(image,dst_sobel_y);
    imshow("dst_sobel_y",dst_sobel_y);

    //prewitt操作
    Mat dst_prewitt_x;
    Mat dst_prewitt_y;

    myconvolution.load_kernal(prewitt_x_kernal);
    myconvolution.convolute(image,dst_prewitt_x);
    imshow("dst_prewitt_x",dst_prewitt_x);

    myconvolution.load_kernal(prewitt_y_kernal);
    myconvolution.convolute(image,dst_prewitt_y);
    imshow("dst_prewitt_y",dst_prewitt_y);
    waitKey(0);
    return 0;
}


        main函数里分别对两个尺寸的均值卷积和高斯卷积进行了测试,高斯卷积的尺寸和其中的参数可以使用Guassian_kernal()函数生成。这里因为程序中只给出了sobel和prewitt两个方向上的卷积核并只分别单纯地做了卷积运算,并没有根据sobel和prewitt算子的完整计算过程算出其卷积图像,如果想知道sobel边缘检测和prewitt边缘检测是什么效果的朋友可以自己试一试。

最后给出效果图:


src原图


dst_guassian——高斯


dst_aver_3——3*3均值


dst_aver_5——5*5均值


dst_prewitt_x——x方向prewitt算子


dst_prewitt_y——y方向prewitt算子


dst_sobel_x——x方向sobel算子


dst_sobel_y——y方向sobel算子


猜你喜欢

转载自blog.csdn.net/qq_32864683/article/details/79748027