1. 程式人生 > >影象卷積操作的手動實現(基於opencv的C++編譯環境)

影象卷積操作的手動實現(基於opencv的C++編譯環境)

        opencv環境下有自帶的filter2D()函式可以實現影象的卷積,自己寫一個卷積函式函式貌似是沒事找事。。。。好吧,事實是這是我們計算機視覺課程上的一項作業。我們很多演算法過程僅僅只呼叫別人寫好的介面,即使原理我們已經清楚,但是真正編寫程式碼的時候很多細節我們可能還是沒有意識到,也許自己再實現一遍是一種深入學習的途徑吧。

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運算元


相關推薦

【python實現神經網路】層Conv2D實現帶stride、padding

關於卷積操作是如何進行的就不必多說了,結合程式碼一步一步來看卷積層是怎麼實現的。 程式碼來源:https://github.com/eriklindernoren/ML-From-Scratch   先看一下其基本的元件函式,首先是determine_padding(filter_shape, ou

影象操作手動實現基於opencv的C++編譯環境

        opencv環境下有自帶的filter2D()函式可以實現影象的卷積,自己寫一個卷積函式函式貌似是沒事找事。。。。好吧,事實是這是我們計算機視覺課程上的一項作業。我們很多演算法過程僅僅只呼叫別人寫好的介面,即使原理我們已經清楚,但是真正編寫程式碼的時候很多細節

影象的fft實現驗證python

1、Caffe的卷積操作時間主要在矩陣乘法,假設一個m*n卷積核,且輸入通道數為1,輸出特徵圖大小為h*w,則乘法個數m*n*h*w,這裡的優化僅限於對矩陣的乘法優化,因此,只要選擇適合的矩陣計算庫就

【深度學習入門】——親手實現影象操作

深度學習中有一個很重要的概念就是卷積神經網路 CNN,卷積神經網路中又有卷積層、池化層的概念。尤其是卷積層,理解難度比較大,雖然書中或者是視訊中都有詳細介紹過它的基礎概念,但對於求知慾望很強烈的我,我總心裡癢癢的,總想親手實現,看看效果,怕的就是自己會眼高手低,

springcloud的Hystrix turbine斷路器聚合監控實現基於springboot2.02版本

events 單個 res source IE lease ans discover www 本文基於方誌朋先生的博客實現:https://blog.csdn.net/forezp/article/details/70233227 一、準本工作 1、工具:Idea,JD

DES加密演算法的java實現基於java類庫

嗯嗯........這個是我新開的部落格上的第一篇的文章,這裡小白希望自己的技術能夠一天比一天好(p≧w≦q),加油! 好吧,現在來一個基於java類庫的DES加密演算法的實現吧~網上不少的程式碼要不執行有問題,要不就是簡簡單單內建一個固定的加密字串就簡單完事了。好吧,我承認我現在是為懶人服務

JNI Demo實現基於IEDA,VS2008

  一、簡介:   JNI是Java Native Interface的縮寫,它提供了若干的API實現了Java和其他語言的通訊(主要是C&C++) 二、步驟概要: 編寫帶有native宣告的方法的java類 使用IDE或javac命令編譯所編寫的java類 使用jav

排序演算法之——優先佇列經典實現基於二叉堆

許多應用都需要處理有序的元素,但有時,我們不要求所有元素都有序,或是一定要一次就將它們排序,許多情況下,我們會收集這些元素裡的最大值或最小值。 這種情況下一個合適的資料結構應該支援兩種操作:插入元素、刪除最大元素。 優先佇列與棧和佇列類似,但它有自己的奇妙之處。 在本文中,會講解基於二叉堆的一種優先佇列

DCT變換及量化的c++實現基於opencv矩陣運算

由於DCT的數學原理不好描述,直接放程式碼了: #include<iostream> #include<fstream> #include<opencv2/core/core.hpp> #include<opencv2/highg

【特徵工程】一種異常值檢測方法、原理、程式碼實現 基於箱線圖

先介紹使用到的方法原理,也就是一種異常檢測的方法。 首先要先了解箱線圖。 箱線圖 箱線圖(Boxplot)也稱箱須圖(Box-whisker Plot),是利用資料中的五個統計量:最小值、第一四分位數、中位數、第三四分位數與最大值來描述資料的一種方法,它也可以

C++ 智慧指標的一個實現基於模板和Shared_ptr

自己實現了一個C++的智慧指標。 基於Shared_ptr來實現,支援預設構造,拷貝構造,移動構造, 引用計數器, 注意智慧指標中存放的指標地址一旦被一個智慧指標物件託管以後,不要再直接拿此地址來初始化其他物件,否則會引發多次洩漏的問題。(所以還是要特別小心) 如果要初始化

STM32CubeIDE下載安裝-GPIO基本配置操作-Debug除錯基於CMSIS DAP Debug

1、在ST官網下載STM32CubeIDE而不是STM32CubeMX,並且STM32CubeIDE是免費的。(STM32CubeIDE不支援中文路徑,不然編譯會出錯) 2、如果你用的是keil開發環境那麼 STM32CubeIDE =(STM32CubeMX + Keil)STM32CubeMX只是一個配置

手動實現神經網路中的操作conv2d

寫這個的原因:一來好像沒怎麼搜到別人手動實現,作為補充;二來鞏固一下基礎。 卷積操作示意 先從一張示意圖說起,卷積基礎概念和操作步驟就不囉嗦了,只講這張圖,大意就是,有in-channel,有out-channel,你需要把in-channel都做卷積操作,然

OpenCV--如何對影象進行操作9

附程式碼如下: import cv2 as cv import numpy as np def blur1(): src = cv.imread("D:/matplotlib/0.jpg") cv.imshow("input",src) blur2 = cv.blur(

吳恩達作業9:神經網路實現手勢數字的識別基於tensorflow

提供資料集程式碼放在cnn_utils.py裡。 import math import numpy as np import h5py import matplotlib.pyplot as plt import tensorflow as tf from tensorfl

caffe原始碼深入學習6:超級詳細的im2col繪圖解析,分析caffe操作的底層實現

在先前的兩篇部落格中,筆者詳細解析了caffe卷積層的定義與實現,可是在conv_layer.cpp與base_conv_layer.cpp中,卷積操作的實現仍然被隱藏,通過im2col_cpu函式和caffe_cpu_gemm函式(後者實現矩陣乘法)實現,在此篇部落格中,筆者旨在向大家展示,caf

0024-利用OpenCV的filter2D函式作影象操作和協相關操作

影象的卷積操作是影象處理中最常用的操作之一,一般是用核算子來實現卷積操作。什麼叫核算子?請移步博文https://blog.csdn.net/lehuoziyuan/article/details/84101788 OpenCV用函式filter2D來實現對影象或矩陣的卷積操作。這個函式本質上做

影象、相關以及在MATLAB中的操作

原文:http://www.cnblogs.com/zjutzz/p/5661543.html 影象卷積、相關以及在MATLAB中的操作 區分卷積和相關 影象處理中常常需要用一個濾波器做空間濾波操作。空間濾波操作有時候也被叫做卷積濾波,或者乾脆叫卷積(離散的卷積,不是微

利用tensorflow實現簡單的神經網路-對程式碼中相關函式介紹——遷移學習小記

  上篇文章對cnn進行了一些介紹,附了完整小例子程式碼,介紹了一部分函式概念,但是對我這樣的新手來說,程式碼中涉及的部分函式還是無法一下子全部理解。於是在本文中將對程式碼中使用的函式繼續進行一一介紹。 具體程式碼見上一篇(二) 一、 #定義輸入的placehoder,x是特徵

理解影象操作的含義

上文用生動的例子來解釋卷積記載了卷積的含義,現在就來看看卷積在影象處理中的應用吧。(ps:本文大部分內容系轉載大神的部落格,現在csdn強制圖片水印,實在感到很無奈!!!) 數字影象處理中卷積 數字影象是一個二維的離散訊號,對數字影象做卷積操作其實就是利用卷