Skip to content

Instantly share code, notes, and snippets.

@pijushbarik
Last active July 28, 2019 10:45
Show Gist options
  • Save pijushbarik/5ef12dd08dd6a347472bf2c966d55aa4 to your computer and use it in GitHub Desktop.
Save pijushbarik/5ef12dd08dd6a347472bf2c966d55aa4 to your computer and use it in GitHub Desktop.
A 2D convolutor function. Convolutes a 2D kernel on a 2D image matrix with Border Reflex 101 method. OpenCV's borderType docs: https://docs.opencv.org/3.1.0/d2/de8/group__core__array.html#gga209f2f4869e304c82d07739337eae7c5ab3c5a6143d8120b95005fa7105a10bb4
/*
* convolute.hpp
*
* Created on: 28-Jul-2019
* Author: Pijush Barik ([email protected])
*
* I didn't found a convolution code (with border reflect 101 method) on the
* internet, may be didn't search well.
* This code is not optimized. If you find it useful please left a review or
* help this code to be optimize or bug free.
*/
#ifndef SRC_CONVOLUTE_HPP_
#define SRC_CONVOLUTE_HPP_
#include <cmath>
/**
* \brief Normalizes a numeric value in to 0-255(uchar) value.
* Used for 8-bit image data.
* @param x A numeric value
* @return If \p x is a negative number, it returns 0.
* If \p x is greater than 255, it returns 255.
* Otherwise \p x is returned.
*/
inline uchar saturate_uchar(double x) {
return (x > 255 ? 255 : (x < 0 ? 0 : x));
}
/**
* \brief Returns a reflected index value.
* @param x Index which to be reflected
* @return If \p x is negative, returns abs(\p x).
* Otherwise returns \p x.
*/
inline int reflect(int x) {
return (x < 0 ? abs(x) : x);
}
/**
* \brief This convolution function applies a 2D convolution kernel in to a 2D image with
* padding. The padding method implemented is OpenCV's BORDER_REFLECT_101. In which
* for edge cases the 0th pixel (border pixel) is the mirror and 1st pixel is reflectd,
* i.e., 1st pixel will be the -1st pixel.
*
* See OpenCV's
* <a href="https://docs.opencv.org/3.1.0/d2/de8/group__core__array.html#gga209f2f4869e304c82d07739337eae7c5ab3c5a6143d8120b95005fa7105a10bb4">BorderType</a> Documentation.
*
* The result of this function has been tested against the OpenCV functions. For
* example, the result of a Gaussian Blur with 3x3 or 5x5 kernel with sigmaX = x
* and sigmaY = y is compared against the result of this function. I have used
* pre-computed kernels with same kernel size and sigma values, the same I used in
* OpenCV GaussinBlur function.
*
* Currently I have tested this on images with 3x3 and 5x5 kernels.
*
* The result is same except some pixel values are different due to the round of
* errors, and the maximum error is 1.
*
* @param in Input image matrix data. Single channel. Mapped into a
* single dimension array.
* @param out Output image matrix data. Single channel. Mapped into a
* single dimension array.
* @param rows Number of rows of the image matrix.
* @param cols Number of columns of the image matrix.
* @param kernel 2D filter kernel. Mapped into a one dimension array.
* @param kernelSize Size of kernel. Greater than 1 and a odd number.
*/
template<class T>
void convolute(
const T *in,
T *out,
int rows,
int cols,
const double kernel[],
int kernelSize
) {
int i, j, m, n, ii, jj, ki, kj;
int offset = kernelSize / 2;
double sum;
for (i = 0; i < rows; i++) {
for (j = 0; j < cols; j++) {
sum = 0.0;
ii = i - offset;
ki = offset;
for (m = 0; m < kernelSize; m++) {
jj = j - offset;
kj = offset;
for (n = 0; n < kernelSize; n++) {
if (ii > rows - 1) {
ii = ii - 2*offset / ki;
ki--;
}
if (jj > cols - 1) {
jj = jj - 2*offset / kj;
kj--;
}
sum += (double) *(in + (reflect(ii) * cols + reflect(jj)))
* kernel[m * kernelSize + n];
if(kj == offset) jj++;
else jj += offset + 1;
}
if(ki == offset) ii++;
else ii += offset + 1;
}
*(out + (i * cols + j)) = saturate_uchar(rint(sum));
}
}
}
#endif /* SRC_CONVOLUTE_HPP_ */
/*
* logger.hpp
*
* Created on: 27-Jul-2019
* Author: Pijush Barik ([email protected])
*/
/**
* \file logger.hpp
* This header file contains some useful logging functions including
* comparisons of two CV Mats.
*/
#ifndef SRC_LOGGER_HPP_
#define SRC_LOGGER_HPP_
#ifndef DOXYGEN_SHOULD_SKIP_THIS
#include <opencv2/core.hpp>
#include <iostream>
#include <iomanip>
using namespace std;
using namespace cv;
void logLine(string file, int line) {
cout << "File: " << file << " Line: " << line << endl;
}
template<class T>
void logValue2(string file, int line, T value) {
cout << "File: " << file << " Line: " << line << endl << "Value: " << value
<< endl;
}
template<class T>
void logValue(string msg, T value) {
cout << msg << ": " << value << endl;
}
template<class T>
void logCvMat(const Mat &mat, int rowsToPrint, int colsToPrint) {
rowsToPrint = rowsToPrint > mat.rows ? mat.rows : rowsToPrint;
colsToPrint = colsToPrint > mat.cols ? mat.cols : colsToPrint;
for (int i = 0; i < rowsToPrint; i++) {
for (int j = 0; j < colsToPrint; j++) {
cout << setw(5) << (int) mat.at<T>(i, j);
}
cout << endl;
}
}
template<class T>
void logCvMatEnd(const Mat &mat, int rowsToPrint, int colsToPrint) {
rowsToPrint = rowsToPrint > mat.rows ? mat.rows : rowsToPrint;
colsToPrint = colsToPrint > mat.cols ? mat.cols : colsToPrint;
for (int i = mat.rows - 1; i >= mat.rows - 1 - rowsToPrint; i--) {
for (int j = mat.cols - 1; j >= mat.cols - 1 - colsToPrint; j--) {
cout << setw(5) << (int) mat.at<T>(i, j);
}
cout << endl;
}
}
template<class T>
void compareCvMats(const Mat &in1, const Mat &in2) {
assert(in1.rows == in2.rows);
assert(in1.cols == in2.cols);
assert(in1.channels() == 1 && in2.channels() == 1);
int i, j, diff, maxDiff = 0, numOfDiffs = 0;
// cout<<"pixels where values are different:"<<endl;
for (i = 0; i < in1.rows; i++) {
for (j = 0; j < in1.cols; j++) {
diff = abs(in1.at<T>(i, j) - in2.at<T>(i, j));
if (diff > 0) {
numOfDiffs++;
if (diff > maxDiff)
maxDiff = diff;
// cout<<i<<" "<<j<<endl;
}
}
}
logValue<int>("Number of different pixel values", numOfDiffs);
logValue<int>("Maximum difference", maxDiff);
}
template<class T>
void compareCvMatsBorders(const Mat &in1, const Mat &in2) {
assert(in1.rows == in2.rows);
assert(in1.cols == in2.cols);
assert(in1.channels() == 1 && in2.channels() == 1);
int i, diff, maxDiff, numOfDiffs;
int rows = in1.rows;
int cols = in1.cols;
cout<<"Border: Top... ";
maxDiff = 0;
numOfDiffs = 0;
for(i = 0; i < cols; i++) {
diff = abs(in1.at<T>(0, i) - in2.at<T>(0, i));
if(diff > 0) {
numOfDiffs++;
if(diff > maxDiff) {
maxDiff = diff;
}
}
}
if(diff == 0) {
cout<<"Same."<<endl;;
} else {
cout<<"Not Same."<<endl;;
logValue<int>("Number of different pixels", numOfDiffs);
logValue<int>("Maximum difference", maxDiff);
}
cout<<"Border: Bottom... ";
maxDiff = 0;
numOfDiffs = 0;
for(i = 0; i < cols; i++) {
diff = abs(in1.at<T>(rows - 1, i) - in2.at<T>(rows - 1, i));
if(diff > 0) {
numOfDiffs++;
if(diff > maxDiff) {
maxDiff = diff;
}
}
}
if(diff == 0) {
cout<<"Same."<<endl;
} else {
cout<<"Not Same."<<endl;
logValue<int>("Number of different pixels", numOfDiffs);
logValue<int>("Maximum difference", maxDiff);
}
cout<<"Border: Left... ";
maxDiff = 0;
numOfDiffs = 0;
for(i = 0; i < rows; i++) {
diff = abs(in1.at<T>(i, 0) - in2.at<T>(i, 0));
if(diff > 0) {
numOfDiffs++;
if(diff > maxDiff) {
maxDiff = diff;
}
}
}
if(diff == 0) {
cout<<"Same."<<endl;
} else {
cout<<"Not Same."<<endl;
logValue<int>("Number of different pixels", numOfDiffs);
logValue<int>("Maximum difference", maxDiff);
}
cout<<"Border: Right... ";
maxDiff = 0;
numOfDiffs = 0;
for(i = 0; i < rows; i++) {
diff = abs(in1.at<T>(i, cols - 1) - in2.at<T>(i, cols - 1));
if(diff > 0) {
numOfDiffs++;
if(diff > maxDiff) {
maxDiff = diff;
}
}
}
if(diff == 0) {
cout<<"Same."<<endl;
} else {
cout<<"Not Same."<<endl;
logValue<int>("Number of different pixels", numOfDiffs);
logValue<int>("Maximum difference", maxDiff);
}
}
#endif /* DOXYGEN_SHOULD_SKIP_THIS */
#endif /* SRC_LOGGER_HPP_ */
/*
* main.cpp
*
* Created on: 27-Jul-2019
* Author: Pijush Barik ([email protected])
*/
/** \file main.cpp
*
* This file is the entry point of the program and contains codes for invoking
* convolution operators for Gaussian Blur and test codes.
*/
#ifndef DOXYGEN_SHOULD_SKIP_THIS
#include <iostream>
#include <opencv2/highgui.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/imgproc.hpp>
#include "logger.hpp"
#include "convolute.hpp"
using namespace std;
int main(int argc, char **argv) {
if(argc < 2) {
cerr<<"Usage: "<<argv[0]<<" color_image_file_path [kernel_size(3 or 5)]"<<endl;
exit(EXIT_FAILURE);
}
int kernelSize = argc > 2 ? atoi(argv[2]) : 3;
string imgfile = argv[1];
// Image read
Mat img = imread(imgfile, IMREAD_COLOR);
const double gaussKernel3x3[] = { 0.102059, 0.115349, 0.102059, 0.115349,
0.130371, 0.115349, 0.102059, 0.115349, 0.102059 };
const double gaussKernel5x5[] = { 0.023528, 0.033969, 0.038393, 0.033969,
0.023528, 0.033969, 0.049045, 0.055432, 0.049045, 0.033969,
0.038393, 0.055432, 0.062651, 0.055432, 0.038393, 0.033969,
0.049045, 0.055432, 0.049045, 0.033969, 0.023528, 0.033969,
0.038393, 0.033969, 0.023528 };
Mat imgray;
cvtColor(img, imgray, COLOR_BGR2GRAY);
Mat gauss1, gauss2(imgray.rows, imgray.cols, CV_8UC1);
GaussianBlur(imgray, gauss1, Size(kernelSize, kernelSize), 2, 2, BORDER_REFLECT_101);
convolute<uchar>(
imgray.data,
gauss2.data,
imgray.rows,
imgray.cols,
kernelSize == 5 ? gaussKernel5x5 : gaussKernel3x3,
kernelSize
);
imshow("original", imgray);
imshow("gauss cv", gauss1);
imshow("gauss custom", gauss2);
waitKey(0);
cout << "Open cv" << endl;
logCvMat<uchar>(gauss1, 10, 10);
cout << endl << "Custom" << endl;
logCvMat<uchar>(gauss2, 10, 10);
cout << endl << "Open cv end" << endl;
logCvMatEnd<uchar>(gauss1, 10, 10);
cout << endl << "Custom end" << endl;
logCvMatEnd<uchar>(gauss2, 10, 10);
cout << endl;
compareCvMats<uchar>(gauss1, gauss2);
compareCvMatsBorders<uchar>(gauss1, gauss2);
}
#endif /* DOXYGEN_SHOULD_SKIP_THIS */
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment