Compute the similarity rate between two Images with opencv/c++ - c++

I'm using OpenCV/C++ to compute the similarity rate between two images. I want to tell the user how much % image A looks like image B.
Let's take a look at the code below :
double getSimilarityRate(const cv::Mat A, const cv::Mat B){
double cpt = 0.0;
cv::Mat imgGray1, imgGray2;
cv::cvtColor(A, imgGray1, CV_BGR2GRAY);
cv::cvtColor(B, imgGray2, CV_BGR2GRAY);
imgGray1 = imgGray1 > 128;
imgGray2 = imgGray2 > 128;
double total = imgGray1.cols * imgGray1.rows;
if(imgGray1.rows > 0 && imgGray1.rows == B.rows && imgGray1.cols > 0 && imgGray1.cols == B.cols){
for(int rows = 0; rows < imgGray1.rows; rows++){
for(int cols = 0; cols < imgGray1.cols; cols++){
if(imgGray1.at<int>(rows, cols) == imgGray2.at<int>(rows,cols)) cpt ++;
}
}
}else{
std::cout << "No similartity between the two images ... [EXIT]" << std::endl;
exit(0);
}
double rate = cpt / total;
return rate * 100.0;
}
int main(void)
{
/* ------------------------------------------ # ALGO GETSIMILARITY BETWEEN 2 IMAGES # -------------------------------------- */
double rate;
string fileNameImage1("C:\\Users\\hugoo\\Documents\\Prog\\NexterMU\\Qt\\OpenCV\\DetectionShapeProgram\\mire.jpg");
cv::Mat image1 = imread(fileNameImage1);
string fileNameImage2("C:\\Users\\hugoo\\Documents\\Prog\\NexterMU\\Qt\\OpenCV\\DetectionShapeProgram\\mire.jpg");
cv::Mat image2 = imread(fileNameImage2);
if(image1.empty() || image2.empty()){
std::cout << "Images couldn't be loaded" << std::endl;
exit(-1);
}
rate = getSimilarityRate(image1, image2) ;
First I convert the matrices from BGR to GRAY. So I have only one channel remaining. (Much more easier to compare).
cv::Mat imgGray1, imgGray2;
cv::cvtColor(A, imgGray1, CV_BGR2GRAY);
cv::cvtColor(B, imgGray2, CV_BGR2GRAY);
Then I make them binary (255 or 0 --> pixel's White or Black) :
imgGray1 = imgGray1 > 128;
imgGray2 = imgGray2 > 128;
In my for loops I pass through each pixel and compare him to other one in the second image.
If it matches I increase a variable (cpt ++).
I compute the rate and turn it to a %, with :
double rate = cpt / total;
return rate * 100.0;
The thing is it doesn't seem to compute correctly, because it doesn't return me the rate value in the console...
I think the problem comes from the at() function maybe I don't use it properly.

I suspect imgGray1.at<int>(rows, cols) should be imgGray1.at<uchar>(rows, cols) instead.
Currently .at() function call has int as a template argument, but typically cv::Mat consist of uchar elements. Are you pretty sure that your image has int elements? If it does consist of uchar elements, then using int template argument will result in accessing memory beyond what corresponds to the image (basically all pointer offsets would now be 4x as large as they should be).
More generally, if you use cv::Mat::at(), you need to use different template arguments depending on the output of cv::Mat::type():
8-bit 3-channel image (CV_8UC3) --> .at<cv::Vec3b>(row, column)
8-bit 1-channel image (CV_8UC1) --> .at<uchar>(row, column)
32-bit 3-channel image (CV_32FC3) --> .at<cv::Vec3f>(row, column)
32-bit 1-channel image (CV_32FC1) --> .at<float>(row, column)
For this reason, if a function should support arbitrary cv::Mat's, one either needs to write a bunch of if-else clauses, or to avoid .at() altogether. In your situation, since imgGray1 and imgGray2 are "binarized", I wonder if rate can be calculated using cv::norm, possibly like so:
// NORM_INF counts the number of non-equal elements.
int num_non_equal = cv::norm(imgGray1, imgGray2, NORM_INF);
double rate = 1.0 - num_non_equal / static_cast<double>(total);

Related

How to find the pixel value that corresponds to a specific number of pixels?

Assume that I have a grayscale image in OpenCV.
I want to find a value so that 5% of pixels in the images have a value greater than it.
I can iterate over pixels and find number of pixels with the same value and then from the result find the value that %5 of pixel are above my value, but I am looking for a faster way to do this. Is there any such technique in OpenCV?
I think histogram would help, but I am not sure how I can use it.
You need to:
Compute the cumulative histogram of your pixel values
Find the bin whose value is greater than 95% (100 - 5) of the total number of pixels.
Given an image uniformly random generated, you get an histogram like:
and the cumulative histogram like (you need to find the first bin whose value is over the blue line):
Then you need to find the proper bin. You can use std::lower_bound function to find the correct value, and std::distance to find the corresponding bin number (aka the value you want to find). (Please note that with lower_bound you'll find the element whose value is greater or equal to the given value. You can use upper_bound to find the element whose value is strictly greater then the given value)
In this case it results to be 242, which make sense for an uniform distribution from 0 to 255, since 255*0.95 = 242.25.
Check the full code:
#include <opencv2\opencv.hpp>
#include <vector>
#include <algorithm>
using namespace std;
using namespace cv;
void drawHist(const vector<int>& data, Mat3b& dst, int binSize = 3, int height = 0, int ref_value = -1)
{
int max_value = *max_element(data.begin(), data.end());
int rows = 0;
int cols = 0;
float scale = 1;
if (height == 0) {
rows = max_value + 10;
}
else {
rows = height;
scale = float(height) / (max_value + 10);
}
cols = data.size() * binSize;
dst = Mat3b(rows, cols, Vec3b(0, 0, 0));
for (int i = 0; i < data.size(); ++i)
{
int h = rows - int(scale * data[i]);
rectangle(dst, Point(i*binSize, h), Point((i + 1)*binSize - 1, rows), (i % 2) ? Scalar(0, 100, 255) : Scalar(0, 0, 255), CV_FILLED);
}
if (ref_value >= 0)
{
int h = rows - int(scale * ref_value);
line(dst, Point(0, h), Point(cols, h), Scalar(255,0,0));
}
}
int main()
{
Mat1b src(100, 100);
randu(src, Scalar(0), Scalar(255));
int percent = 5; // percent % of pixel values are above a val
int val; // I need to find this value
int n = src.rows * src.cols; // Total number of pixels
int th = cvRound((100 - percent) / 100.f * n); // Number of pixels below val
// Histogram
vector<int> hist(256, 0);
for (int r = 0; r < src.rows; ++r) {
for (int c = 0; c < src.cols; ++c) {
hist[src(r, c)]++;
}
}
// Cumulative histogram
vector<int> cum = hist;
for (int i = 1; i < hist.size(); ++i) {
cum[i] = cum[i - 1] + hist[i];
}
// lower_bound returns an iterator pointing to the first element
// that is not less than (i.e. greater or equal to) th.
val = distance(cum.begin(), lower_bound(cum.begin(), cum.end(), th));
// Plot histograms
Mat3b plotHist, plotCum;
drawHist(hist, plotHist, 3, 300);
drawHist(cum, plotCum, 3, 300, *lower_bound(cum.begin(), cum.end(), th));
cout << "Value: " << val;
imshow("Hist", plotHist);
imshow("Cum", plotCum);
waitKey();
return 0;
}
Note
The histogram drawing function is an upgrade from a former version I posted here
You can use calcHist to compute the histograms, but I personally find easier to use the aforementioned method for 1D histograms.
1) Determine the height and the width of the image, h and w.
2) Determine what 5% of the total number of pixels is (X)...
X = int(h * w * 0.05)
3) Start at the brightest bin in the histogram. Set total T = 0.
4) Add the number of pixels in this bin to your total T. If T is greater than X, you are finished and the value you want is the lower limit of the range of the current histogram bin.
3) Move to the next darker bin in your histogram. Goto 4.

SANITYCHECK and maxUsedValInHistogramData

I'm trying to reimplement matlab imregionalmax() in C++ with openCV, i did search on the site and found some interesting answers here Find local maxima in grayscale image using OpenCV and the best one so far belongs to Doga Siyli, but there are 2 "weird" functions. The first one is: SANITYCHECK(squareSize,3,1) and the other one is : maxUsedValInHistogramData(dst,false); .
(by "weird" i mean i don't think these two are OpenCV's function.)
My question is:
I replaced the SANITYCHECK(squaresize,3,1) with assert(squareSize >= 3); and maxUsedValInHistogramData(dst,false); with minmaxLoc but the program didn't work ,especially the second one because minmaxLoc return global value while Doga's intention is to find the local values.
So how do i make the code work ?
I am new to C++ and OpenCV,and i'm still learning,any help willl be greatly appreciated.
Here is his code for a closer look( he did explain it quite clear)
void localMaxima(cv::Mat src, cv::Mat &dst, int squareSize)
{
if (squareSize == 0)
{
dst = src.clone();
return;
}
Mat m0;
dst = src.clone();
Point maxLoc(0, 0);
//1.Be sure to have at least 3x3 for at least looking at 1 pixel close neighbours
// Also the window must be <odd>x<odd>
SANITYCHECK(squareSize, 3, 1);
int sqrCenter = (squareSize - 1) / 2;
//2.Create the localWindow mask to get things done faster
// When we find a local maxima we will multiply the subwindow with this MASK
// So that we will not search for those 0 values again and again
Mat localWindowMask = Mat::zeros(Size(squareSize, squareSize), CV_8U);//boolean
localWindowMask.at<unsigned char>(sqrCenter, sqrCenter) = 1;
//3.Find the threshold value to threshold the image
//this function here returns the peak of histogram of picture
//the picture is a thresholded picture it will have a lot of zero values in it
//so that the second boolean variable says :
// (boolean) ? "return peak even if it is at 0" : "return peak discarding 0"
int thrshld = maxUsedValInHistogramData(dst, false);
threshold(dst, m0, thrshld, 1, THRESH_BINARY);
//4.Now delete all thresholded values from picture
dst = dst.mul(m0);
//put the src in the middle of the big array
for (int row = sqrCenter; row<dst.size().height - sqrCenter; row++)
for (int col = sqrCenter; col<dst.size().width - sqrCenter; col++)
{
//1.if the value is zero it can not be a local maxima
if (dst.at<unsigned char>(row, col) == 0)
continue;
//2.the value at (row,col) is not 0 so it can be a local maxima point
m0 = dst.colRange(col - sqrCenter, col + sqrCenter + 1).rowRange(row - sqrCenter, row + sqrCenter + 1);
minMaxLoc(m0, NULL, NULL, NULL, &maxLoc);
//if the maximum location of this subWindow is at center
//it means we found the local maxima
//so we should delete the surrounding values which lies in the subWindow area
//hence we will not try to find if a point is at localMaxima when already found a neighbour was
if ((maxLoc.x == sqrCenter) && (maxLoc.y == sqrCenter))
{
m0 = m0.mul(localWindowMask);
//we can skip the values that we already made 0 by the above function
col += sqrCenter;
}
}
}
the maxUsedValInHistogramData function is used to improve computation time by defining a threshold thrshld.Then cv::threshold is used to set all the pixel of the dst image under thrshld to zero.
According to the explanation, the threshold is defined by the histogram max. This method is efficient, because a large part of the image, set to zero, is skipped:
if (dst.at<unsigned char>(row, col) == 0)
continue;
However it also mean that the local maxima under thrshld are not detected
You can remove these three lines, and the function should work correctly but slower:
int thrshld = maxUsedValInHistogramData(dst, false);
threshold(dst, m0, thrshld, 1, THRESH_BINARY);
dst = dst.mul(m0);
or write a function that detect the histogram maxima of the input image

opencv C++ neural network predict() function throws "Bad argument" error

I have managed to train a neural network to recognize numbers in an image and have saved the network parameters to an .xml file.
However, when testing the network against a new image the code fails at the predict() stage with the error:
OpenCV Error: Bad argument (Both input and output must be floating-point matrices of the same type and have the same number of rows) in CvANN_MLP::predict, file ........\opencv\modules\ml\src\ann_mlp.cpp, line 279.
ann_mlp.cpp line 279 is:
if( !CV_IS_MAT(_inputs) || !CV_IS_MAT(_outputs) ||
!CV_ARE_TYPES_EQ(_inputs,_outputs) ||
(CV_MAT_TYPE(_inputs->type) != CV_32FC1 &&
CV_MAT_TYPE(_inputs->type) != CV_64FC1) ||
_inputs->rows != _outputs->rows )
CV_Error( CV_StsBadArg, "Both input and output must be floating-point matrices "
"of the same type and have the same number of rows" );
I have checked input rows by running this code:
cv::Size s = newVec.size();
int rows = s.height;
int cols = s.width;
cout << "newVec dimensions: " << rows << " x " << cols << endl;
...and it comes out with the expected 1 x 900 vector / matrix.
I have set the input and output matrices to be CV_32FC1 as per the error dialog like this:
Input matrix
cv::Mat newVec(1, 900, CV_32FC1);
newVec = crop_img.reshape(0, 1); //reshape / unroll image to vector
CvMat n = newVec;
newVec = cv::Mat(&n);
Output matrix
cv::Mat classOut = cvCreateMatHeader(1, CLASSES, CV_32FC1);
And I try to run the prediction function like this:
CvANN_MLP* nnetwork = new CvANN_MLP;
nnetwork->load("nnetwork.xml", "nnetwork");
int maxIndex = 0;
cv::Mat classOut = cvCreateMatHeader(1, CLASSES, CV_32FC1);
//prediction
nnetwork->predict(newVec, classOut);
float value;
float maxValue = classOut.at<float>(0, 0);
for (int index = 1; index<CLASSES; index++)
{
value = classOut.at<float>(0, index);
if (value>maxValue)
{
maxValue = value;
maxIndex = index;
}
}
Any ideas? Much appreciated...
I suspect the problem is your input, not your output.
First it's important to understand that OpenCV deserves a lot of the blame for this, not you. Their C++ API is quite mediocre, and it caused major confusion to you.
See, normally in C++ when you define a 1x900 matrix of floats, it stays a matrix of floats. C++ has strong type safety.
OpenCV does not. If you assign a matrix of bytes to a matrix of floats, the latter will change its type (!).
Your code initializes newVec to such a matrix of floats, then assigns a second matrix, and then yet another matrix. I suspect that crop_img is still an image, i.e. 8 bits. Reshaping it will make it 1x900, but not floating point. That's the job of .convertTo.

Low contrast image segmentation

I have problem with low contrast image segmentation.
Task is to find surface defects. They are visible (defects are always dark areas) but the contrast of image is very low.
Below two samples.
I have tried enhance contrast and then tresholding:
Mat tmp1 = imread("C:\\framesRoi\\311.bmp",0);
stretchContrast(tmp1);
threshold(tmp1,tmp1,75,255,THRESH_BINARY);
where stretch contrast impl:
int minValue = 255, maxValue = 0;
const int l = sourceImg.cols * sourceImg.rows * sourceImg.channels();
if(sourceImg.isContinuous())
{
uchar* ptr = sourceImg.ptr<uchar>(0);
for(int i = 0; i < l; ++i)
{
if(ptr[i] < minValue)
{
minValue = ptr[i];
}
if(ptr[i] > maxValue)
{
maxValue = ptr[i];
}
}
}
cout<<"min: "<<minValue<<";"<<"max value: "<<maxValue<<endl;
const int magicThreshold = 10;
if(sourceImg.isContinuous())
{
uchar* ptr = sourceImg.ptr<uchar>(0);
for(int i = 0; i < l; ++i)
{
ptr[i] = 255 * (ptr[i]-minValue)/(maxValue - minValue);
}
}
But this approach failed. There are many false detections and not all defects are detected:
Here is zip with test frames: https://dl.dropboxusercontent.com/u/47015140/testFrames.rar
Try clustering the image by gray level using a clustering method such as kmeans. Below I've used kmeans directly on the images without any gray level transformations (using 3 clusters gave me better results). You should be able to improve results by clustering a preprocessed image using methods outlined in the comments.
Shape of the clusters may slightly vary due to the randomness of kmeans.
Now if you take connected components of the clustered image and calculate the average gray level of those regions, the defects should have a lower average than the other regions.
I did clustering part in Matlab.
im = imread('r2SOV.png');%Uy1Fq r2SOV
gr = im;
size = size(gr);
% perform closing using a 5x5 circular structuring element
sel = strel('disk', 2, 4);
mcl = imclose(gr, sel);
% cluster gray levels using kmeans: using 3 clusters
x = double(mcl(:));
idx = kmeans(x, 3);
cl = reshape(idx, size);
figure, imshow(label2rgb(cl))
As people said in your comment, you can change the brightness in a negative way and push up the contrast.
Moreover, the sharpen filter is also very useful for your case. You can do this in OpenCV.
I think you should try adaptiveThreshold function with a large window.
#include "opencv2/opencv.hpp"
using namespace cv;
int main(int argc,char** argv )
{
Mat im = imread("c:/data/img1.png",0);
cv::namedWindow("ctrl");
int win=62;
int th=2100;
cv::createTrackbar( "win", "ctrl", &win, 500);
cv::createTrackbar( "th", "ctrl", &th, 10000);
while(true)
{
Mat thresh;
medianBlur(im,thresh,15);//helps smooth out smaller noises, which you could also remove by size instead of this way
adaptiveThreshold(thresh,thresh,255,ADAPTIVE_THRESH_MEAN_C,THRESH_BINARY,win*2+1,( th/1000.));
imshow("thresh",thresh);
if(waitKey(1)==27)
exit(0);
}
}
all results here (http://www.datafilehost.com/d/99e3d86c) You might also want to take a look at imagej which implements a bunch of auto-threshold algorithms. I think what you need is something that takes local image information into account.

Implementing FFT low-pass filter in C with FFTW

I am trying to create a very simple C++ program that given an argument in range [0-100] applies a low-pass filter to a grayscale image that should "compress" it proprotionally to the value of the given argument.
I am using the FFTW library.
I have some doubts about how I define the frequency threshold, cut. Is there any more effective way to define such value?
//fftw_complex *fft
//double[] magnitude
// . . .
int percent = 100;
if (percent < 0 || percent > 100) {
cerr << "Compression rate must be a value between 0 and 100." << endl;
return -1;
}
double cut =(double)(w*h) * ((double)percent / (double)100);
for (i = 0; i < (w * h); i++) {
magnitude[i] = sqrt(pow(fft[i][0], 2.0) + pow(fft[i][1], 2.0));
if (magnitude[i] < cut) {
fft[i][0] = 0.0;
fft[i][1] = 0.0;
}
}
Update1:
I've changed my code to this, but again I'm not sure this is a proper way to filter frequencies. The image is surely compressed, but non-square images are messed up and setting compression to 100% isn't the real maximum compression available (I can go up to ~140%).
Here you can find an image of what I see now.
int cX = w/2;
int cY = h/2;
cout<<"TEST "<<((double)percent/(double)100)*h<<endl;
for(i = 0; i<(w*h);i++){
int row = i/s;
int col = i%s;
int distance = sqrt((col-cX)*(col-cX)+(row-cY)*(row-cY));
if(distance<((double)percent/(double)100)*min(cX,cY)){
fft[i][0] = 0.0;
fft[i][1] = 0.0;
}
}
This is not a low-pass filter at all. A low-pass filter passes low frequencies, i.e. it removes fine details (blurring). You obviously need a 2D FFT for that.
This code just removes random bits, essentially.
[edit]
The new code looks a lot more like a low-pass filter. The 141% setting is expected: the diagonal of a square is sqrt(2)=1.41 times its side. Converting an index into a row/column pair should use the image width, not some random unexplained s.
I don't know where your zero frequency is located. That should be easy to spot (largest value) but it might be in (0,0) instead of (w/2,h/2)