您的位置:首页 > 编程语言 > C语言/C++

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

2018-03-29 21:12 666 查看
        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算子
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息