Search code examples
c++opencvconvolutionblurgaussianblur

gaussian smoothing output misaligned


I am trying to perform gaussian smoothing on this image without using any opencv function (except displaying the image).

input image

However, the output I got after convoluting the image with the gaussian kernel is as follow:

output image

The output image seems to have misaligned and looks very weird. Any idea what is happening?

Generate gaussian kernel:

double gaussian(int x, int y,double sigma){
    return (1/(2*M_PI*pow(sigma,2)))*exp(-1*(pow(x,2)+pow(y,2))/(2*pow(sigma,2)));
}



double generateFilter(vector<vector<double>> & kernel,int width,double sigma){
    int value = 0;
    double total =0;

    if(width%2 == 1){
        value = (width-1)/2;
    }else{
        value = width/2;
    }

    double smallest = gaussian(-1*value,-1*value,sigma);

    for(int i = -1*value; i<=value; i++){
        vector<double> temp;
        for(int k = -1*value; k<=value; k++){
            int gVal = round(gaussian(i,k,sigma)/smallest);
            temp.push_back(gVal);
            total += gVal;

        }
        kernel.push_back(temp);

    }

    cout<<total<<endl;

    return total;


}

Convolution:

vector<vector<unsigned int>>  convolution(vector<vector<unsigned int>> src,  vector<vector<double>> kernel,double total){
    int kCenterX = floor(kernel.size() / 2); //center of kernel
    int kCenterY = kCenterX; //center of kernel
    int kRows = kernel.size(); //height of kernel
    int kCols = kRows; //width of kernel
    int imgRows = src.size(); //height of input image
    int imgCols = src[0].size(); //width of input image
    vector<vector<unsigned int>> dst = vector<vector<unsigned int>> (imgRows, vector<unsigned int>(imgCols ,0));


    for ( size_t row = 0; row < imgRows; row++ ) { 
        for ( size_t col = 0; col < imgCols; col++ ) {
        float accumulation = 0;
        float weightsum = 0; 
        for ( int i = -1*kCenterX; i <= 1*kCenterX; i++ ) {
            for ( int j = -1*kCenterY; j <= 1*kCenterY; j++ ) {
                int k = 0;

                if((row+i)>=0 && (row+i)<imgRows && (col+j)>=0 && (col+j)<imgCols){
                    k = src[row+i][col+j];
                    weightsum += kernel[kCenterX+i][kCenterY+j];
                }

                accumulation += k * kernel[kCenterX +i][kCenterY+j];


            }
        } 
        dst[row][col] = round(accumulation/weightsum);
        }

    }

    return dst;
}

Thank you.


Solution

  • The convolution function is basically correct, so the issue is with the input and output format.

    • Make sure you are reading the image as Grayscale (and not RGB):

      cv::Mat I = cv::imread("img.png", cv::IMREAD_GRAYSCALE);
      
    • You are passing vector<vector<unsigned int>> argument to convolution.
      I can't say if it's part of the problem or not, but it's recommended to pass argument of type cv::Mat (and return cv::Mat):

      cv::Mat convolution(cv::Mat src, vector<vector<double>> kernel, double total)
      

      I assume you can convert the input to and from vector<vector<unsigned int>>, but it's not necessary.

    Here is a working code sample:

    #include <vector>
    #include <iostream>
    
    #include "opencv2/opencv.hpp"
    #include "opencv2/highgui.hpp"
    
    using namespace std;
    
    
    double gaussian(int x, int y, double sigma) {
        return (1 / (2 * 3.141592653589793*pow(sigma, 2)))*exp(-1 * (pow(x, 2) + pow(y, 2)) / (2 * pow(sigma, 2)));
    }
    
    
    double generateFilter(vector<vector<double>> & kernel, int width, double sigma)
    {
        int value = 0;
        double total = 0;
    
        if (width % 2 == 1) {
            value = (width - 1) / 2;
        }
        else {
            value = width / 2;
        }
    
        double smallest = gaussian(-1 * value, -1 * value, sigma);
    
        for (int i = -1 * value; i <= value; i++) {
            vector<double> temp;
            for (int k = -1 * value; k <= value; k++) {
                int gVal = round(gaussian(i, k, sigma) / smallest);
                temp.push_back(gVal);
                total += gVal;
            }
            kernel.push_back(temp);    
        }
    
        cout << total << endl;
    
        return total;
    }
    
    
    //vector<vector<unsigned int>>  convolution(vector<vector<unsigned int>> src,  vector<vector<double>> kernel, double total) {
    cv::Mat convolution(cv::Mat src, vector<vector<double>> kernel, double total) {
        int kCenterX = floor(kernel.size() / 2); //center of kernel
        int kCenterY = kCenterX; //center of kernel
        int kRows = kernel.size(); //height of kernel
        int kCols = kRows; //width of kernel
        int imgRows = src.rows;//src.size(); //height of input image
        int imgCols = src.cols;//src[0].size(); //width of input image
        //vector<vector<unsigned int>> dst = vector<vector<unsigned int>> (imgRows, vector<unsigned int>(imgCols ,0));
        cv::Mat dst = cv::Mat::zeros(src.size(), CV_8UC1);  //Create destination matrix, and fill with zeros (dst is Grayscale image with byte per pixel).
    
        for (size_t row = 0; row < imgRows; row++) {
            for (size_t col = 0; col < imgCols; col++) {
                double accumulation = 0;
                double weightsum = 0;
                for (int i = -1 * kCenterX; i <= 1 * kCenterX; i++) {
                    for (int j = -1 * kCenterY; j <= 1 * kCenterY; j++) {
                        int k = 0;
    
                        if ((row + i) >= 0 && (row + i) < imgRows && (col + j) >= 0 && (col + j) < imgCols) {
                            //k = src[row+i][col+j];
                            k = (int)src.at<uchar>(row + i, col + j);   //Read pixel from row [row + i] and column [col + j]
                            weightsum += kernel[kCenterX + i][kCenterY + j];
                        }
    
                        accumulation += (double)k * kernel[kCenterX + i][kCenterY + j];
                    }
                }
                //dst[row][col] = round(accumulation/weightsum);
                dst.at<uchar>(row, col) = (uchar)round(accumulation / weightsum);   //Write pixel from to row [row] and column [col]
    
                //dst.at<uchar>(row, col) = src.at<uchar>(row, col);
            }
    
        }
    
        return dst;
    }
    
    
    int main()
    {
        vector<vector<double>> kernel;
        double total = generateFilter(kernel, 11, 3.0);
    
        //Read input image as Grayscale (one byte per pixel).
        cv::Mat I = cv::imread("img.png", cv::IMREAD_GRAYSCALE);
    
        cv::Mat J = convolution(I, kernel, total);
    
        //Display input and output
        cv::imshow("I", I);
        cv::imshow("J", J);
        cv::waitKey(0);
        cv::destroyAllWindows();
    
        return 0;
    }
    

    Result:
    enter image description here