opencv 例程講解5 ---- 如何實現卷積運算

         圖像可以看作一個矩陣,在矩陣上面做掩模操作是一個很普遍的事情,它實際上可以看做利用一個小矩陣對一個大矩陣進行卷積運算,這個小矩陣便是掩模,也稱核(kernel)。很多功能的實現都依賴於掩模操作,如圖像平滑,銳化,以及腐蝕膨脹等形態學的一些操作。下面我們來看下Opencv中的例程——(TUTORIAL) mat_mask_operations。

例程中使用了一個3x3的掩模對圖像進行操作,實現銳化效果。先簡單介紹下使用到的掩模,如下所示。

可以看出,爲得到新圖像中(i,j)位置的灰度值,需要藉助一個卷積窗口,窗口大小與掩模大小一致,由以(i,j)爲中心的圖像像素組成,則卷積運算可以看做是卷積窗口中各像素點的加權和,每個像素點的加權大小對應於掩模中相應位置的值。上圖中,一次卷積需要1次乘法,4次加減法。由於不只是對當前像素點遍歷,還需要藉助相鄰像素進行計算,所以例程中使用的圖像遍歷方法是C的[ ] 下標訪問方式。相應的,opencv對於掩模操作有自己的函數,filter2D,下面便是性能對比。


源代碼中每種方法只允許了一遍,爲了測試的準確性,我在程序中加了一個循環,這個結果是100次運算的平均時間。可以看出使用opencv的filter2D函數比自己手工打造的掩模操作性能上提高了39%

下面我們看下手工打造的掩模操作是如何實現的

void Sharpen(const Mat& myImage,Mat& Result)
{
    CV_Assert(myImage.depth() == CV_8U);  // accept only uchar images   // CV_Assert 是cv定義的斷言格式,如果條件不符合,會中斷程序,拋出異常

    const int nChannels = myImage.channels();
    Result.create(myImage.size(),myImage.type());

    for(int j = 1 ; j < myImage.rows-1; ++j)                             // 第一行和最後一行無法計算
    {
        const uchar* previous = myImage.ptr<uchar>(j - 1);      //   上一行數據的指針
        const uchar* current  = myImage.ptr<uchar>(j    );        //   當前行數據的指針
        const uchar* next     = myImage.ptr<uchar>(j + 1);       //  下一行數據的指針

        uchar* output = Result.ptr<uchar>(j);                            // 輸出圖像當前列數據的指針

        for(int i= nChannels;i < nChannels*(myImage.cols-1); ++i)   // 同理,忽略第一列和最後一列像素
        {
            *output++ = saturate_cast<uchar>(5*current[i]
                         -current[i-nChannels] - current[i+nChannels] - previous[i] - next[i]);         // 計算新值
        }
    }
    // 將邊界設爲0
    Result.row(0).setTo(Scalar(0));
    Result.row(Result.rows-1).setTo(Scalar(0));
    Result.col(0).setTo(Scalar(0));
    Result.col(Result.cols-1).setTo(Scalar(0));
}

可以看出,例程中利用了3個指針,分別用於定位在3行中的像素,實現對相鄰像素的操作。輸入圖像(myImage)中的像素通過[ ]下標讀取,而輸出圖像(output)中通過++操作進行指針移位操作。下面是輸入圖像和輸出圖像的對比。可以看出這個掩模實現了銳化的效果。通過改變掩模矩陣中的值,我們還能實現平滑濾波以及形態學操作等。


我們再來看看,如何調用opencv自帶的filter2D函數

    Mat kern = (Mat_<char>(3,3) <<  0, -1,  0,                          // 生成一個掩模核 大小爲 3x3 , 通過<<  輸入到矩陣Mat_<char> 中,然後隱式轉換成Mat類型
                                   -1,  5, -1,
                                    0, -1,  0);
    t = (double)getTickCount();                                               //Opencv中的一種計時方法,獲取當前Tick數
	for (int i=0; i<times; i++)
	{
		 filter2D(I, K, I.depth(), kern );
       }   
    t = ((double)getTickCount() - t)/getTickFrequency();    // 需要除掉Tick的頻率,單位爲秒
    cout << "Built-in filter2D time passed in seconds:      " << t/times << endl;

filter2D函數的原型爲,最小需要提供4個參數,第一個參數爲輸入,第二參數爲輸出,第三參數表示depth,可以爲CV_8U, CV_16U, CV_8S, CV_16S, CV_32F, CV_64F, 第四個參數爲輸入的掩模核。

void filter2D( InputArray src, OutputArray dst, int ddepth,
                            InputArray kernel, Point anchor=Point(-1,-1),
                            double delta=0, int borderType=BORDER_DEFAULT );
 這裏介紹下opencv中的輸入輸出類型, InputArray , OutputArray。

以下一段是opencv reference文檔中對InputArray, OutputArray的描述。

Many OpenCV functions process dense 2-dimensional or multi-dimensional numerical arrays. Usually, such functions take cpp:class:Mat as parameters, but in some cases it’s more convenient to use std::vector<> (for a point set, for example) or Matx<> (for 3x3 homography matrix and such). To avoid many duplicates in the API, special “proxy” classes have been introduced. The base “proxy” class is InputArray. It is used for passing read-only arrays on a function input. The derived from InputArray class OutputArray is used to specify an output array for a function. Normally, you should not care of those intermediate types (and you should not declare variables of those types explicitly) - it will all just work automatically. You can assume that instead of InputArray/OutputArray you can always use Mat, std::vector<>, Matx<>, Vec<> or Scalar. When a function has an optional input or output array, and you do not have or do not want one, pass cv::noArray().

按照上面的描述,在opencv 函數處理的大部分是2維或者多維的數據,即Mat 類型,但是對於一些點集,或者3x3的homography矩陣,使用vector<>,或者Matx<>類型更加方便,如果對每一種數據類型都寫一個接口API的話,會造成API的代碼的過多重複,因此,opencv引入 代理(“proxy”)類型  InputArray,OutputArray,對輸入輸出類型進行統一管理。

在源碼中我們可以找到如下的定義, 它被typedef爲 類 _InputArray 的常引用,這也是描述中InputArray爲 read-only 的原因。

typedef const _InputArray& InputArray;
typedef const _OutputArray& OutputArray;

如果繼續看 _InputArray  的定義,

//////////////////////// Input/Output Array Arguments /////////////////////////////////

/*!
 Proxy datatype for passing Mat's and vector<>'s as input parameters
 */
class CV_EXPORTS _InputArray
{
public:
    enum {
        KIND_SHIFT = 16,
        FIXED_TYPE = 0x8000 << KIND_SHIFT,
        FIXED_SIZE = 0x4000 << KIND_SHIFT,
        KIND_MASK = 31 << KIND_SHIFT,

        NONE              = 0 << KIND_SHIFT,
        MAT               = 1 << KIND_SHIFT,
        MATX              = 2 << KIND_SHIFT,
        STD_VECTOR        = 3 << KIND_SHIFT,
        STD_VECTOR_VECTOR = 4 << KIND_SHIFT,
        STD_VECTOR_MAT    = 5 << KIND_SHIFT,
        EXPR              = 6 << KIND_SHIFT,
        OPENGL_BUFFER     = 7 << KIND_SHIFT,
        CUDA_MEM          = 8 << KIND_SHIFT,
        GPU_MAT           = 9 << KIND_SHIFT,
        OCL_MAT           =10 << KIND_SHIFT,
        UMAT              =11 << KIND_SHIFT,
        STD_VECTOR_UMAT   =12 << KIND_SHIFT,
        UEXPR             =13 << KIND_SHIFT
    };

    _InputArray();
    _InputArray(int _flags, void* _obj);
    _InputArray(const Mat& m);
    _InputArray(const MatExpr& expr);
    _InputArray(const std::vector<Mat>& vec);
    template<typename _Tp> _InputArray(const Mat_<_Tp>& m);
    template<typename _Tp> _InputArray(const std::vector<_Tp>& vec);
    template<typename _Tp> _InputArray(const std::vector<std::vector<_Tp> >& vec);
    template<typename _Tp> _InputArray(const std::vector<Mat_<_Tp> >& vec);
    template<typename _Tp> _InputArray(const _Tp* vec, int n);
    template<typename _Tp, int m, int n> _InputArray(const Matx<_Tp, m, n>& matx);
    _InputArray(const double& val);
    _InputArray(const cuda::GpuMat& d_mat);
    _InputArray(const ogl::Buffer& buf);
    _InputArray(const cuda::CudaMem& cuda_mem);
    template<typename _Tp> _InputArray(const cudev::GpuMat_<_Tp>& m);
    _InputArray(const UMat& um);
    _InputArray(const std::vector<UMat>& umv);
    _InputArray(const UMatExpr& uexpr);

    virtual Mat getMat(int idx=-1) const;
    virtual UMat getUMat(int idx=-1) const;
    virtual void getMatVector(std::vector<Mat>& mv) const;
    virtual cuda::GpuMat getGpuMat() const;
    virtual ogl::Buffer getOGlBuffer() const;
    void* getObj() const;

    virtual int kind() const;
    virtual int dims(int i=-1) const;
    virtual Size size(int i=-1) const;
    virtual int sizend(int* sz, int i=-1) const;
    virtual bool sameSize(const _InputArray& arr) const;
    virtual size_t total(int i=-1) const;
    virtual int type(int i=-1) const;
    virtual int depth(int i=-1) const;
    virtual int channels(int i=-1) const;
    virtual bool isContinuous(int i=-1) const;
    virtual bool empty() const;
    virtual void copyTo(const _OutputArray& arr) const;
    virtual size_t offset(int i=-1) const;
    virtual size_t step(int i=-1) const;
    bool isMat() const;
    bool isUMat() const;
    bool isMatVectot() const;
    bool isUMatVector() const;
    bool isMatx();

    virtual ~_InputArray();

protected:
    int flags;
    void* obj;
    Size sz;

    void init(int _flags, const void* _obj);
    void init(int _flags, const void* _obj, Size _sz);
};

可以看出InputArray 中的數據只有3個,一個Size 類型, 一個 flags 用來記錄傳人的數據類型,一個則爲傳人的對象指針;提供了多種類型的隱式轉換,將輸入類型轉換成InputArray傳遞到函數中,然後在函數中,可以利用getMat() 等方法獲得輸入對象的指針。

OutputArray 爲InputArray的派生類,主要增加了create()等方法,下面爲OutputArray的源碼。

/*!
 Proxy datatype for passing Mat's and vector<>'s as input parameters
 */
class CV_EXPORTS _OutputArray : public _InputArray
{
public:
    enum
    {
        DEPTH_MASK_8U = 1 << CV_8U,
        DEPTH_MASK_8S = 1 << CV_8S,
        DEPTH_MASK_16U = 1 << CV_16U,
        DEPTH_MASK_16S = 1 << CV_16S,
        DEPTH_MASK_32S = 1 << CV_32S,
        DEPTH_MASK_32F = 1 << CV_32F,
        DEPTH_MASK_64F = 1 << CV_64F,
        DEPTH_MASK_ALL = (DEPTH_MASK_64F<<1)-1,
        DEPTH_MASK_ALL_BUT_8S = DEPTH_MASK_ALL & ~DEPTH_MASK_8S,
        DEPTH_MASK_FLT = DEPTH_MASK_32F + DEPTH_MASK_64F
    };

    _OutputArray();
    _OutputArray(int _flags, void* _obj);
    _OutputArray(Mat& m);
    _OutputArray(std::vector<Mat>& vec);
    _OutputArray(cuda::GpuMat& d_mat);
    _OutputArray(ogl::Buffer& buf);
    _OutputArray(cuda::CudaMem& cuda_mem);
    template<typename _Tp> _OutputArray(cudev::GpuMat_<_Tp>& m);
    template<typename _Tp> _OutputArray(std::vector<_Tp>& vec);
    template<typename _Tp> _OutputArray(std::vector<std::vector<_Tp> >& vec);
    template<typename _Tp> _OutputArray(std::vector<Mat_<_Tp> >& vec);
    template<typename _Tp> _OutputArray(Mat_<_Tp>& m);
    template<typename _Tp> _OutputArray(_Tp* vec, int n);
    template<typename _Tp, int m, int n> _OutputArray(Matx<_Tp, m, n>& matx);
    _OutputArray(UMat& m);
    _OutputArray(std::vector<UMat>& vec);

    _OutputArray(const Mat& m);
    _OutputArray(const std::vector<Mat>& vec);
    _OutputArray(const cuda::GpuMat& d_mat);
    _OutputArray(const ogl::Buffer& buf);
    _OutputArray(const cuda::CudaMem& cuda_mem);
    template<typename _Tp> _OutputArray(const cudev::GpuMat_<_Tp>& m);
    template<typename _Tp> _OutputArray(const std::vector<_Tp>& vec);
    template<typename _Tp> _OutputArray(const std::vector<std::vector<_Tp> >& vec);
    template<typename _Tp> _OutputArray(const std::vector<Mat_<_Tp> >& vec);
    template<typename _Tp> _OutputArray(const Mat_<_Tp>& m);
    template<typename _Tp> _OutputArray(const _Tp* vec, int n);
    template<typename _Tp, int m, int n> _OutputArray(const Matx<_Tp, m, n>& matx);
    _OutputArray(const UMat& m);
    _OutputArray(const std::vector<UMat>& vec);

    virtual bool fixedSize() const;
    virtual bool fixedType() const;
    virtual bool needed() const;
    virtual Mat& getMatRef(int i=-1) const;
    virtual cuda::GpuMat& getGpuMatRef() const;
    virtual ogl::Buffer& getOGlBufferRef() const;
    virtual cuda::CudaMem& getCudaMemRef() const;
    virtual void create(Size sz, int type, int i=-1, bool allowTransposed=false, int fixedDepthMask=0) const;
    virtual void create(int rows, int cols, int type, int i=-1, bool allowTransposed=false, int fixedDepthMask=0) const;
    virtual void create(int dims, const int* size, int type, int i=-1, bool allowTransposed=false, int fixedDepthMask=0) const;
    virtual void createSameSize(const _InputArray& arr, int mtype) const;
    virtual void release() const;
    virtual void clear() const;
    virtual void setTo(const _InputArray& value) const;
};

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章