4

I was learning filters in OpenCV, but I'm a little confused about the Laplacian filter. My result is very different from the Laplacian filter in OpenCV lib.

For first, I use a Gaussian filter for the image:

Mat filtroGauss(Mat src){
    Mat gauss = src.clone();
    Mat temp(src.rows+2,src.cols+2,DataType<uchar>::type);

    int y,x;
    for (y=0; y<src.rows; y++){
    for (x=0; x<src.cols; x++) temp.at<uchar>(y+1,x+1) = src.at<uchar>(y,x);
        }

    int mask[lenMask*lenMask];
    mask[0] = mask[2] = mask[6] = mask[8] = 1;
    mask[1] = mask[3] = mask[5] = mask[7] = 2;
    mask[4] = 4;

    int denominatore = 0;
    for (int i=0; i<lenMask*lenMask; i++) denominatore += mask[i];

    int value[lenMask*lenMask];
    for(y=0; y<src.rows; y++){
        for (x=0; x<src.cols; x++){
            value[0] = temp.at<uchar>(y-1,x-1)*mask[0];
            value[1] = temp.at<uchar>(y-1,x)*mask[1];
            value[2] = temp.at<uchar>(y-1,x+1)*mask[2];
            value[3] = temp.at<uchar>(y,x-1)*mask[3];
            value[4] = temp.at<uchar>(y,x)*mask[4];
            value[5] = temp.at<uchar>(y,x+1)*mask[5];
            value[6] = temp.at<uchar>(y+1,x-1)*mask[6];
            value[7] = temp.at<uchar>(y+1,x)*mask[7];
            value[8] = temp.at<uchar>(y+1,x+1)*mask[8];

            int avg = 0;
            for(int i=0; i<lenMask*lenMask; i++)avg+=value[i];
            avg = avg/denominatore;

            gauss.at<uchar>(y,x) = avg;
        }
    }
    return gauss;
}

Then I use the Laplacian function:

L(y,x) = f(y-1,x) + f(y+1,x) + f(y,x-1) + f(y,x+1) + 4*f(y,x)

Mat filtroLaplace(Mat src){
    Mat output = src.clone();
    Mat temp = src.clone();

    int y,x;
    for (y =1; y<src.rows-1; y++){
        for(x =1; x<src.cols-1; x++){

            output.at<uchar>(y,x) = temp.at<uchar>(y-1,x) + temp.at<uchar>(y+1,x) + temp.at<uchar>(y,x-1) + temp.at<uchar>(y,x+1) -4*( temp.at<uchar>(y,x));
        }
    }
    return output;
    }

And here is the final result from my code:


OpenCV result:

hippietrail
  • 15,848
  • 18
  • 99
  • 158
ShottyNo
  • 63
  • 1
  • 7
  • Can you share result of your Gaussian and openCV gaussian as well – Garvita Tiwari Aug 27 '18 at 15:20
  • Looks like your results are scaled. – SilverMonkey Aug 27 '18 at 15:26
  • `filtroGauss` will try to access `temp` out of bounds -- the second set of loops start with `x=0, y=0` and the first thing you do is `temp.at(y-1,x-1)`... – Dan Mašek Aug 27 '18 at 16:11
  • 1
    The problem in `filtroLaplace` is the implicit cast when assigning the sum of weighed components to `output`. Specifically, the sum may in some cases be negative, or greater > 255 -- OpenCV performs saturation (i.e. it clips the values to range [0..255], you overflow. Simple fix -- use `cv::saturate_cast`. – Dan Mašek Aug 27 '18 at 16:25

1 Answers1

6

Let's rewrite the function a little, so it's easier to discuss:

cv::Mat filtroLaplace(cv::Mat src)
{
    cv::Mat output = src.clone();

    for (int y = 1; y < src.rows - 1; y++) {
        for (int x = 1; x < src.cols - 1; x++) {
            int sum = src.at<uchar>(y - 1, x)
                + src.at<uchar>(y + 1, x)
                + src.at<uchar>(y, x - 1)
                + src.at<uchar>(y, x + 1)
                - 4 * src.at<uchar>(y, x);

            output.at<uchar>(y, x) = sum;
        }
    }
    return output;
}

The source of your problem is sum. Let's examine its range in scope of this algorithm, by taking the two extremes:

  • Black pixel, surrounded by 4 white. That means 255 + 255 + 255 + 255 - 4 * 0 = 1020.
  • White pixel, surrounded by 4 black. That means 0 + 0 + 0 + 0 - 4 * 255 = -1020.

When you perform output.at<uchar>(y, x) = sum; there's an implicit cast of the int back to unsigned char -- the high order bits simply get chopped off and the value overflows.

The correct approach to handle this situation (which OpenCV takes), is to perform saturation before the actual cast. Essentially

if (sum < 0) {
    sum = 0;
} else if (sum > 255) {
    sum = 255;
}

OpenCV provides function cv::saturate_cast<T> to do just this.


There's an additional problem that you're not handling the edge rows/columns of the input image -- you just leave them at the original value. Since you're not asking about that, I'll leave solving that as an excercise to the reader.


Code:

cv::Mat filtroLaplace(cv::Mat src)
{
    cv::Mat output = src.clone();

    for (int y = 1; y < src.rows - 1; y++) {
        for (int x = 1; x < src.cols - 1; x++) {
            int sum = src.at<uchar>(y - 1, x)
                + src.at<uchar>(y + 1, x)
                + src.at<uchar>(y, x - 1)
                + src.at<uchar>(y, x + 1)
                - 4 * src.at<uchar>(y, x);

            output.at<uchar>(y, x) = cv::saturate_cast<uchar>(sum);
        }
    }
    return output;
}

Sample input:

Output of corrected filtroLaplace:

Output of cv::Laplacian:

Dan Mašek
  • 17,852
  • 6
  • 57
  • 85
  • 1
    But since negative values are significant and relevant here, a better solution is to **(a)** scale the result from the [-1020,1020] range to the [0,255] range, or **(b)** use a signed 16-bit integer as output type. – Cris Luengo Aug 27 '18 at 17:13