简体   繁体   中英

Opencv - how does the filter2D() method actually work?

I did look for the source code to Filter2D but could not find it. Neither could Visual c++. Are there any experts on the filter2D algorithm here? I know how it's supposed to work but not how it actually works. I made my own filter2d() function to test things, and the results are substantially different from opencvs filter2D(). Here's my code:

Mat myfilter2d(Mat input, Mat filter){

Mat dst = input.clone();
cout << " filter data successfully found.  Rows:" << filter.rows << " cols:" << filter.cols << " channels:" << filter.channels() << "\n";
cout << " input data successfully found.  Rows:" << input.rows << " cols:" << input.cols << " channels:" << input.channels() << "\n";

for (int i = 0-(filter.rows/2);i<input.rows-(filter.rows/2);i++){
    for (int j = 0-(filter.cols/2);j<input.cols-(filter.cols/2);j++){  //adding k and l to i and j will make up the difference and allow us to process the whole image
        float filtertotal = 0;
        for (int k = 0; k < filter.rows;k++){
            for (int l = 0; l < filter.rows;l++){
                if(i+k >= 0 && i+k < input.rows && j+l >= 0 && j+l < input.cols){  //don't try to process pixels off the endge of the map
                    float a = input.at<uchar>(i+k,j+l);
                    float b = filter.at<float>(k,l);
                    float product = a * b;
                    filtertotal += product;
                }
            }
        }
        //filter all proccessed for this pixel, write it to dst
        st.at<uchar>(i+(filter.rows/2),j+(filter.cols/2)) = filtertotal;

    }
}
return dst;
}

Anybody see anything wrong with my implementation? (besides being slow)

Here is my execution:

  cvtColor(src,src_grey,CV_BGR2GRAY);
  Mat dst = myfilter2d(src_grey,filter);
  imshow("myfilter2d",dst);
  filter2D(src_grey,dst2,-1,filter);
  imshow("filter2d",dst2);

Here is my kernel:

float megapixelarray[basesize][basesize] = {
            {1,1,-1,1,1},
            {1,1,-1,1,1},
            {1,1,1,1,1},
            {1,1,-1,1,1},
            {1,1,-1,1,1}
            };

And here are the (substantially different) results:

Thoughts, anyone?

EDIT: Thanks to Brians answer I added this code:

//normalize the kernel so its sum = 1
Scalar mysum = sum(dst);
dst = dst / mysum[0];   //make sure its not 0
dst = dst * -1;  //show negetive

and filter2d worked better. Certain filters give an exact match, and other filters, like the Sobel, fail miserably.

I'm getting close to the actual algorithm, but not there yet. Anyone else with any ideas?

I think the issue is probably one of scale: if your input image is an 8-bit image, most of the time the convolution will produce a value that overflows the maximum value 255.

In your implementation it looks like you are getting the wrapped-around value, but most OpenCV functions handle overflow by capping to the maximum (or minimum) value. That explains why most of the output of OpenCV's function is white, and also why you are getting concentric shapes in your output too.

To account for this, normalize your megapixelarray filter by dividing every value by the entire sum of the filter (ie make sure that the sum of the filter values is 1):

For example, instead of this filter (sum = 10):

1 1 1
1 2 1
1 1 1

Try this filter (sum = 1):

0.1 0.1 0.1
0.1 0.2 0.1
0.1 0.1 0.1

Here is my solution for creating the filter2D manually:

#include <iostream>
#include <opencv2/opencv.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>

using namespace cv;
using namespace std;

int main(int argc, const char * argv[]) {    
    Mat img;
    Mat img_conv;
    Mat my_kernel;
    Mat my_conv;

    // Controlling if the image is loaded correctly
    img = imread("my_image.jpg",CV_LOAD_IMAGE_COLOR);
    if(! img.data )
    {
        cout <<  "Could not open or find the image" << std::endl ;
        return -1;
    }
    imshow("original image", img);
    img.convertTo(img, CV_64FC3);

    int kernel_size;   // permitted sizes: 3, 5, 7, 9 etc
    cout << "Select the size of kernel (it should be an odd number from 3 onwards): \n" << endl;
    cin >> kernel_size;

    // Defining the kernel here
    int selection;
    cout << "Select the type of kernel:\n" << "1. Identity Operator \n2. Mean Filter \n3. Spatial shift \n4. Sharpening\n-> ";
    cin >> selection;
    switch (selection){
        case 1:
            my_kernel = (Mat_<double>(kernel_size,kernel_size) << 0, 0, 0, 0, 1, 0, 0, 0, 0);
            break;
        case 2:
            my_kernel = (Mat_<double>(kernel_size,kernel_size) << 1, 1, 1, 1, 1, 1, 1, 1, 1) / ( kernel_size * kernel_size);
            break;
        case 3:
            my_kernel = (Mat_<double>(kernel_size,kernel_size) << 0, 0, 0, 0, 0, 1, 0, 0, 0);
            break;
        case 4:
            my_kernel = (Mat_<double>(kernel_size,kernel_size) << -1, -1, -1, -1, 17, -1, -1, -1, -1) / ( kernel_size * kernel_size);
            break;
        default:
            cerr << "Invalid selection";
            return 1;
            break;
    }
    cout << "my kernel:\n "<<my_kernel << endl;

    // Adding the countour of nulls around the original image, to avoid border problems during convolution
    img_conv = Mat::Mat(img.rows + my_kernel.rows - 1, img.cols + my_kernel.cols - 1, CV_64FC3, CV_RGB(0,0,0));

    for (int x=0; x<img.rows; x++) {
        for (int y=0; y<img.cols; y++) {
                img_conv.at<Vec3d>(x+1,y+1)[0] = img.at<Vec3d>(x,y)[0];
                img_conv.at<Vec3d>(x+1,y+1)[1] = img.at<Vec3d>(x,y)[1];
                img_conv.at<Vec3d>(x+1,y+1)[2] = img.at<Vec3d>(x,y)[2];
        }
    }

    //Performing the convolution
    my_conv = Mat::Mat(img.rows, img.cols, CV_64FC3, CV_RGB(0,0,0));
    for (int x=(my_kernel.rows-1)/2; x<img_conv.rows-((my_kernel.rows-1)/2); x++) {
        for (int y=(my_kernel.cols-1)/2; y<img_conv.cols-((my_kernel.cols-1)/2); y++) {
            double comp_1=0;
            double comp_2=0;
            double comp_3=0;
                for (int u=-(my_kernel.rows-1)/2; u<=(my_kernel.rows-1)/2; u++) {
                    for (int v=-(my_kernel.cols-1)/2; v<=(my_kernel.cols-1)/2; v++) {
                        comp_1 = comp_1 + ( img_conv.at<Vec3d>(x+u,y+v)[0] * my_kernel.at<double>(u + ((my_kernel.rows-1)/2) ,v + ((my_kernel.cols-1)/2)));
                        comp_2 = comp_2 + ( img_conv.at<Vec3d>(x+u,y+v)[1] * my_kernel.at<double>(u + ((my_kernel.rows-1)/2),v + ((my_kernel.cols-1)/2)));
                        comp_3 = comp_3 + ( img_conv.at<Vec3d>(x+u,y+v)[2] * my_kernel.at<double>(u +  ((my_kernel.rows-1)/2),v + ((my_kernel.cols-1)/2)));
                    }
                }
            my_conv.at<Vec3d>(x-((my_kernel.rows-1)/2),y-(my_kernel.cols-1)/2)[0] = comp_1;
            my_conv.at<Vec3d>(x-((my_kernel.rows-1)/2),y-(my_kernel.cols-1)/2)[1] = comp_2;
            my_conv.at<Vec3d>(x-((my_kernel.rows-1)/2),y-(my_kernel.cols-1)/2)[2] = comp_3;
        }
    }
    my_conv.convertTo(my_conv, CV_8UC3);
    imshow("convolution - manual", my_conv);

    // Performing the filtering using the opencv funtions
    Mat dst;
    filter2D(img, dst, -1 , my_kernel, Point( -1, -1 ), 0, BORDER_DEFAULT );
    dst.convertTo(dst, CV_8UC3);
    imshow("convlution - opencv", dst);


    waitKey();
    return 0;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM