图像处理中卷积的实现

来源:互联网 发布:高跟鞋推荐 知乎 编辑:程序博客网 时间:2024/05/18 00:29

一、用C解释原理

假设图像(宽6高4),一个卷积核(宽3高3),如下:

unsigned char src[24] ={    1,2,3,4,5,6,    1,1,1,1,1,1,    2,1,2,1,2,1,    4,5,6,1,2,3};
float kernel[9] = {    -1., 0., 1.,     -2., 0., 2.,    -3., 0., 3.};

卷积的步骤为:1、对kernel做逆时针180°旋转;2、遍历src,加权求和

C代码(亲测通过):

bool conv2d(unsigned char* src, float* dst, int nRows, int nCols, float* kernel, int nKernelRows, int nKernelCols){if (nKernelRows % 2 == 0 || nKernelCols % 2 == 0) {cout << "The kernel size must be an odd" << endl;return false;}// we take the center as the anchor of kernelint nCenRow = nKernelRows / 2;int nCenCol = nKernelCols / 2;int nCenOff = nCenRow * nKernelCols + nCenCol;// turn rotate 180 degrees anti-clockwise to the kernel image.float* RotateKernel = (float*)malloc(sizeof(float) * nKernelCols * nKernelRows);for (int r = 0; r < nKernelRows; r++) {for (int c = 0; c < nKernelCols; c++) {RotateKernel[(nKernelRows - r) * nKernelCols - 1 - c] = kernel[c + r * nKernelRows];}}// test code - show rotate kernel/*for(int r = 0; r < nKernelRows; r++) {for (int c = 0; c < nKernelCols; c++) {cout << setw(8) << setiosflags(ios::fixed) << setprecision(2)<< RotateKernel[r * nKernelCols + c];}cout << endl;} */for (int r = 0; r < nRows; r++) {for (int c = 0; c < nCols; c++) {float fTemp = 0.f;for (int kr = -nCenRow; kr <= nCenRow; kr++) {for (int kc = -nCenCol; kc <= nCenCol; kc++) {// border type - constant 0if (r + kr >= 0 && r + kr < nRows && c + kc >= 0 && c + kc < nCols) {fTemp += src[(r + kr) * nCols + c + kc] * 1.f * RotateKernel[kr * nKernelCols + kc + nCenOff];/*cout << setw(4) << r << setw(4) << c << setw(4) << kr << setw(4) << kc << setw(4) << src[(r + kr) * nCols + c + kc] * 1.f<< setw(4) << RotateKernel[kr * nKernelCols + kc + nCenOff]<< setw(4) << fTemp<< endl; */}}}dst[r * nCols + c] = fTemp;}}free(RotateKernel);return true;}// testint main(int argc, char** argv){cout << ">> ----" << "\n" << endl;int nRows = 4;int nCols = 6;unsigned char src[24] ={1,2,3,4,5,6,1,1,1,1,1,1,2,1,2,1,2,1,4,5,6,1,2,3};float dst[24];memset(dst, 0, 24*sizeof(float));float kernel[9] = {-1., 0., 1.,    -2., 0., 2.,   -3., 0., 3.};int nKernelRows = 3;int nKernelCols = 3;conv2d(src, dst, nRows, nCols, kernel, nKernelRows, nKernelRows);// test codefor(int r = 0; r < nRows; r++) {for (int c = 0; c < nCols; c++) {cout << setw(8) << setiosflags(ios::fixed) << setprecision(2) << dst[r * nCols + c];}cout << endl;}cout << "\n" << ">> ----" << endl;return 0;}

二、OpenCV卷积

上边代码仅用来说明原理,实际中,这样的代码效率很渣哈!不想自己优化的,就用OpenCV的现成的!

void filter2D(Mat src,              Mat dst,              int ddepth,              Mat kernel,              Point anchor,              double delta,              int borderType);
src: (input) This is the image that you want to convolve.dst: (input) This image stores the final result of the convolution. It should be the same size and have the same number of channels as src. This can be the same as src (in place operation is supported).ddepth: (input) This is the desired bit depth of the final result (8, 16, 32, etc). It it is negative, the depth is the same as the source image.kernel: (input) The convolution kernel used to convolve the source image. This has to be a single channel, floating point matrix. If you want to apply different kernels to different channels, you need to split the channels, and convolve each of them them individually.anchor: (input) The relative position of the anchor in the kernel matrix. If this is set to (-1,-1), the center of the kernel is used as the anchor point.delta: (input) A value that is added to all pixels after convolution.borderType: (input) Possible values for this include:BORDER_REPLICATEBORDER_CONSTANTBORDER_REFLECT_101BORDER_WARPBORDER_TRANSPARENTBORDER_DEFAULT (same as reflect)BORDER_ISOLATED
borderType的具体解释:

enum BorderTypes {      BORDER_CONSTANT    = 0, //!< `iiiiii|abcdefgh|iiiiiii`  with some specified `i`      BORDER_REPLICATE   = 1, //!< `aaaaaa|abcdefgh|hhhhhhh`      BORDER_REFLECT     = 2, //!< `fedcba|abcdefgh|hgfedcb`      BORDER_WRAP        = 3, //!< `cdefgh|abcdefgh|abcdefg`      BORDER_REFLECT_101 = 4, //!< `gfedcb|abcdefgh|gfedcba`      BORDER_TRANSPARENT = 5, //!< `uvwxyz|absdefgh|ijklmno`        BORDER_REFLECT101  = BORDER_REFLECT_101, //!< same as BORDER_REFLECT_101      BORDER_DEFAULT     = BORDER_REFLECT_101, //!< same as BORDER_REFLECT_101      BORDER_ISOLATED    = 16 //!< do not look outside of ROI  };
但是,用filter2D时,要时刻清醒一点:

这个算子实际就算的是相关(correlation),如果你的卷积核是对称(symmetrical)的,则在数学结果上,卷积核相关的值是相同的。但如果卷积核不对称,必须向上边那样,先做个180°旋转!

另外,如果卷积核很大的话,该函数会自动采用离散傅里叶变换算法来加速计算!