Faster way to pick up the blob at a given point - c++

I am trying to create an equivalent function for matlab's bwselect. So, I want to display the blob (which contains the points I will provide) and mask the rest.
Here's what I have tried.
cv::Mat bwselect(cv::Mat matImg, int x, int y)
{
cv::Mat img_labels, stats, centroids, mask;
if (matImg.data)
{
int numOfLables = connectedComponentsWithStats(matImg, img_labels, stats, centroids, 8, CV_32S);
if (numOfLables > 1)
{
for (int i = 1; i < numOfLables; i++)
{
mask = cv::Mat::zeros(img_labels.size(), CV_8UC1);
mask = mask | (img_labels == i);
if (mask.at<uchar>(y, x) > 0)
{
break;
}
}
}
}
return mask;
}
It does the job. But it's slow. Is there any faster and efficient way to do this?

If the input image is large and if it contains many objects, then the bottleneck could arise because you are allocating/deallocating a large mask buffer a lot of times.
Furthermore, if you call this function lots of times, it would be wise to call connectedComponentsWithStats only once and then use its results as additional input for your function.
I would suggest that you replace this entire loop
for (int i = 1; i < numOfLabels; i++){/*...*/}
with this
// img_labels data type is CV_32S
int label_at_pos = img_labels.at<int>(y, x);
if (label_at_pos > 0)
{
// create mask here and return it
}
EDIT: I made a correction to my code sample above. The connectedComponentsWithStats computes labels image that contains integer values by default.

Related

Compute the similarity rate between two Images with opencv/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);

Opencv obatin certain pixel RGB value based on mask

My title may not be clear enough, but please look carefully on the following description.Thanks in advance.
I have a RGB image and a binary mask image:
Mat img = imread("test.jpg")
Mat mask = Mat::zeros(img.rows, img.cols, CV_8U);
Give some ones to the mask, assume the number of ones is N. Now the nonzero coordinates are known, based on these coordinates, we can surely obtain the corresponding pixel RGB value of the origin image.I know this can be accomplished by the following code:
Mat colors = Mat::zeros(N, 3, CV_8U);
int counter = 0;
for (int i = 0; i < mask.rows; i++)
{
for (int j = 0; j < mask.cols; j++)
{
if (mask.at<uchar>(i, j) == 1)
{
colors.at<uchar>(counter, 0) = img.at<Vec3b>(i, j)[0];
colors.at<uchar>(counter, 1) = img.at<Vec3b>(i, j)[1];
colors.at<uchar>(counter, 2) = img.at<Vec3b>(i, j)[2];
counter++;
}
}
}
And the coords will be as follows:
enter image description here
However, this two layer of for loop costs too much time. I was wondering if there is a faster method to obatin colors, hope you guys can understand what I was trying to convey.
PS:If I can use python, this can be done in only one sentence:
colors = img[mask == 1]
The .at() method is the slowest way to access Mat values in C++. Fastest is to use pointers, but best practice is an iterator. See the OpenCV tutorial on scanning images.
Just a note, even though Python's syntax is nice for something like this, it still has to loop through all of the elements at the end of the day---and since it has some overhead before this, it's de-facto slower than C++ loops with pointers. You necessarily need to loop through all the elements regardless of your library, you're doing comparisons with the mask for every element.
If you are flexible with using any other open source library using C++, try Armadillo. You can do all linear algebra operations with it and also, you can reduce above code to one line(similar to your Python code snippet).
Or
Try findNonZero()function and find all coordinates in image containing non-zero values. Check this: https://stackoverflow.com/a/19244484/7514664
Compile with optimization enabled, try profiling this version and tell us if it is faster:
vector<Vec3b> colors;
if (img.isContinuous() && mask.isContinuous()) {
auto pimg = img.ptr<Vec3b>();
for (auto pmask = mask.datastart; pmask < mask.dataend; ++pmask, ++pimg) {
if (*pmask)
colors.emplace_back(*pimg);
}
}
else {
for (int r = 0; r < img.rows; ++r) {
auto prowimg = img.ptr<Vec3b>(r);
auto prowmask = img.ptr(r);
for (int c = 0; c < img.cols; ++c) {
if (prowmask[c])
colors.emplace_back(prowimg[c]);
}
}
}
If you know the size of colors, reserve the space for it beforehand.

Getting values for specific frequencies in a short time fourier transform

I'm trying to use C++ to recreate the spectrogram function used by Matlab. The function uses a Short Time Fourier Transform (STFT). I found some C++ code here that performs a STFT. The code seems to work perfectly for all frequencies but I only want a few. I found this post for a similar question with the following answer:
Just take the inner product of your data with a complex exponential at
the frequency of interest. If g is your data, then just substitute for
f the value of the frequency you want (e.g., 1, 3, 10, ...)
Having no background in mathematics, I can't figure out how to do this. The inner product part seems simple enough from the Wikipedia page but I have absolutely no idea what he means by (with regard to the formula for a DFT)
a complex exponential at frequency of interest
Could someone explain how I might be able to do this? My data structure after the STFT is a matrix filled with complex numbers. I just don't know how to extract my desired frequencies.
Relevant function, where window is Hamming, and vector of desired frequencies isn't yet an input because I don't know what to do with them:
Matrix<complex<double>> ShortTimeFourierTransform::Calculate(const vector<double> &signal,
const vector<double> &window, int windowSize, int hopSize)
{
int signalLength = signal.size();
int nOverlap = hopSize;
int cols = (signal.size() - nOverlap) / (windowSize - nOverlap);
Matrix<complex<double>> results(window.size(), cols);
int chunkPosition = 0;
int readIndex;
// Should we stop reading in chunks?
bool shouldStop = false;
int numChunksCompleted = 0;
int i;
// Process each chunk of the signal
while (chunkPosition < signalLength && !shouldStop)
{
// Copy the chunk into our buffer
for (i = 0; i < windowSize; i++)
{
readIndex = chunkPosition + i;
if (readIndex < signalLength)
{
// Note the windowing!
data[i][0] = signal[readIndex] * window[i];
data[i][1] = 0.0;
}
else
{
// we have read beyond the signal, so zero-pad it!
data[i][0] = 0.0;
data[i][1] = 0.0;
shouldStop = true;
}
}
// Perform the FFT on our chunk
fftw_execute(plan_forward);
// Copy the first (windowSize/2 + 1) data points into your spectrogram.
// We do this because the FFT output is mirrored about the nyquist
// frequency, so the second half of the data is redundant. This is how
// Matlab's spectrogram routine works.
for (i = 0; i < windowSize / 2 + 1; i++)
{
double real = fft_result[i][0];
double imaginary = fft_result[i][1];
results(i, numChunksCompleted) = complex<double>(real, imaginary);
}
chunkPosition += hopSize;
numChunksCompleted++;
} // Excuse the formatting, the while ends here.
return results;
}
Look up the Goertzel algorithm or filter for example code that uses the computational equivalent of an inner product against a complex exponential to measure the presence or magnitude of a specific stationary sinusoidal frequency in a signal. Performance or resolution will depend on the length of the filter and your signal.

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.

Finding Local Maxima Grayscale Image opencv

I am trying to create my personal Blob Detection algorithm
As far as I know I first must create different Gaussian Kernels with different sigmas (which I am doing using Mat kernel= getGaussianKernel(x,y);) Then get the Laplacian of that kernel and then filter the Image with that so I create my scalespace. Now I need to find the Local Maximas in each result Image of the scalespace. But I cannot seem to find a proper way to do so.... my Code so far is
vector <Point> GetLocalMaxima(const cv::Mat Src,int MatchingSize, int Threshold)
{
vector <Point> vMaxLoc(0);
if ((MatchingSize % 2 == 0) ) // MatchingSize has to be "odd" and > 0
{
return vMaxLoc;
}
vMaxLoc.reserve(100); // Reserve place for fast access
Mat ProcessImg = Src.clone();
int W = Src.cols;
int H = Src.rows;
int SearchWidth = W - MatchingSize;
int SearchHeight = H - MatchingSize;
int MatchingSquareCenter = MatchingSize/2;
uchar* pProcess = (uchar *) ProcessImg.data; // The pointer to image Data
int Shift = MatchingSquareCenter * ( W + 1);
int k = 0;
for(int y=0; y < SearchHeight; ++y)
{
int m = k + Shift;
for(int x=0;x < SearchWidth ; ++x)
{
if (pProcess[m++] >= Threshold)
{
Point LocMax;
Mat mROI(ProcessImg, Rect(x,y,MatchingSize,MatchingSize));
minMaxLoc(mROI,NULL,NULL,NULL,&LocMax);
if (LocMax.x == MatchingSquareCenter && LocMax.y == MatchingSquareCenter)
{
vMaxLoc.push_back(Point( x+LocMax.x,y + LocMax.y ));
// imshow("W1",mROI);cvWaitKey(0); //For gebug
}
}
}
k += W;
}
return vMaxLoc;
}
which I found in this thread here, which it supposedly returns a vector of points where the maximas are. it does return a vector of points but all the x and y coordinates of each point are always -17891602... What to do???
Please if you are to lead me in something else other than correcting my code be informative because I know nothing about opencv. I am just learning
The problem here is that your LocMax point is declared inside the inner loop and never initialized, so it's returning garbage data every time. If you look back at the StackOverflow question you linked, you'll see that their similar variable Point maxLoc(0,0) is declared at the top and constructed to point at the middle of the search window. It only needs to be initialized once. Subsequent loop iterations will replace the value with the minMaxLoc function result.
In summary, remove this line in your inner loop:
Point LocMax; // delete this
And add a slightly altered version near the top:
vector <Point> vMaxLoc(0); // This was your original first line
Point LocMax(0,0); // your new second line
That should get you started anyway.
I found it guys. The problem was my threshold was too high. I do not understand why it gave me negative points instead of zero points but lowering the threshold worked