Last active
July 28, 2019 10:45
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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_ */ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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_ */ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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