【问题标题】:Differences in filter2D implementationfilter2D 实现的差异
【发布时间】:2019-06-14 11:11:31
【问题描述】:

我试图实现convolute2D(OpenCV 中的filter2D)并想出了以下代码。

Mat convolute2D(Mat image, double** kernel, int W){
    Mat filtered_image = image.clone();
    // find center position of kernel (half of kernel size)
    int kCenterX = W / 2;
    int kCenterY = W / 2;
    int xx = 0;
    int yy = 0;
    cout << endl << "Performing convolution .." << endl;
    cout << "Image Size : " << image.rows << ", " << image.cols <<endl;
    for (int i = 0; i < image.rows; ++i){
        for (int j = 0; j < image.cols; ++j){
            for(int x = 0; x < W; ++x){
                xx = W - 1 - x;
                for(int y = 0; y < W; ++y){
                    yy = W - 1 - y;
                    int ii = i + (x - kCenterX);
                    int jj = j + (y - kCenterY);
                    if( ii >= 0 && ii < image.rows && jj >= 0 && jj < image.cols) {
                        filtered_image.at<uchar>(Point(j, i)) += image.at<uchar>(Point(jj, ii)) * kernel[xx][yy];
                    }

                }
            }
        }
    }
    return filtered_image;
}

假设我们总是有一个方形内核。但我的结果与filter2D 有很大不同。是因为可能溢出还是我的实现有问题?

谢谢

【问题讨论】:

    标签: c++ opencv image-processing computer-vision convolution


    【解决方案1】:

    您的代码有两个问题:

    1. 在向其添加值之前,不要将输出图像设置为零。因此,您正在计算“输入 + 过滤输入”,而不仅仅是“过滤输入”。

    2. 假设 kernel 的值非常小,“输入像素 * 内核值”可能会产生一个小数字,写入 uchar 时会向下舍入。将内核的这些值中的每一个相加,您最终会得到一个太低的结果。

    我建议你这样做:

    double res = 0;
    for(int x = 0; x < W; ++x){
       int xx = W - 1 - x;
       for(int y = 0; y < W; ++y){
          int yy = W - 1 - y;
          int ii = i + (x - kCenterX);
          int jj = j + (y - kCenterY);
          if( ii >= 0 && ii < image.rows && jj >= 0 && jj < image.cols) {
             res += image.at<uchar>(Point(jj, ii)) * kernel[xx][yy];
          }
       }
    }
    filtered_image.at<uchar>(Point(j, i)) = res;
    

    这同时解决了这两个问题。此外,这应该会快一点,因为访问输出图像有一些开销。

    为了更快的速度,请考虑检查越界读取(内部循环中的if)会显着减慢您的代码速度,并且对于大多数像素来说完全没有必要(因为图像附近的像素很少边缘)。相反,您可以将循环拆分为[0,kCenterX][kCenterX,image.rows-kCenterX][image.rows-kCenterX,image.rows]。中间循环通常是迄今为止最大的,不需要检查越界读取。

    【讨论】:

    • 非常感谢您提供描述性但简洁的答案!第一个问题是一个很好的捕获。我也没有意识到可能的小值问题。在完成我的研究生院后,我才开始自己实现 CV 算法,这节奏相当快!
    【解决方案2】:

    并使用 cv::saturate_cast 正确分配给 uchar,例如:

    filtered_image.at<uchar>(Point(j, i)) = cv::saturate_cast<uchar>(res);
    

    【讨论】:

      猜你喜欢
      • 2020-07-04
      • 1970-01-01
      • 1970-01-01
      • 2011-08-24
      • 1970-01-01
      • 1970-01-01
      • 2014-04-18
      • 2012-01-28
      • 1970-01-01
      相关资源
      最近更新 更多