I am using OpenCV and I have the following problem I wish to solve on an image. However, my question doesn't have to be restricted to OpenCV if it is an issue, it could simply be more general c++ on, say, vectors.
The problem
I want to map the pixel values of an input image (or elements of an input vector) into an output image (or vector) of same size. Each element of the output should contain the percentile* of the corresponding element in the input.
My question is
How would I code an implementation of this problem? (In c++ or OpenCV)
Example
I will show an example of a one-dimensional image, just for simplicity.
Input:
(1, 2, 10, 3, 4, 5, 6, 12, 7)
Output*: (0.11, 0.22, 0.89, 0.33, 0.44, 0.56, 0.67, 1.00, 0.78)
Performance?
I'm writing code for an analysis of images that may be a few hundred by a few hundred. I assume that my problem is possible in O(n log n), but I don't think even O(n^2) would be an issue (where n is the total number of elements in the image/vector). However, if precise percentiles cause too much issues with the complexity, I'm okay with having a certain number of bins instead.
(*) I know that there are a few different ways of conceptualizing percentile, whether you round up or down et cetera. This is of no importance to me. Whatever way works.
I am not sure it this is what you're looking for but this a naive implementation of percentile for image pixel values.
cv::Mat image = cv::imread("image.jpg",cv::IMREAD_UNCHANGED);
// convert image to gray
cv::Mat gray;
cv::cvtColor(image, gray, cv::COLOR_BGR2GRAY);
// calculate histogram for every pixel value (i.e [0 - 255])
cv::Mat hist;
int histSize = 256;
float range[] = { 0, 256 } ;
const float* histRange = { range };
bool uniform = true; bool accumulate = false;
cv::calcHist( &gray, 1, 0, cv::Mat(), hist, 1, &histSize, &histRange, uniform, accumulate );
// total pixels in image
float totalPixels = gray.cols * gray.rows;
// calculate percentage of every histogram bin (i.e: pixel value [0 - 255])
// the 'bins' variable holds pairs of (int pixelValue, float percentage)
std::vector<std::pair<int, float>> bins;
float percentage;
for(int i = 0; i < 256; ++i)
{
percentage = (hist.at<float>(i,0)*100.0)/totalPixels;
bins.push_back(std::make_pair(i, percentage));
}
// sort the bins according to percentage
sort(bins.begin(), bins.end(),comparator());
// compute percentile for a pixel value
int pixel = 185;
float sum = 0;
for (auto b : bins)
{
if(b.first != pixel)
sum += b.second;
else
{
sum += b.second/2;
break;
}
}
std::cout<<"Percentile for pixel("<<pixel<<"): "<<sum<<std::endl;
Related
i have this part of code. im beginner and want to understand this code. can someone explain me what happens if i set cumulative to true. Where is the difference to false. Would be nice if someone could explain me the difference.
i just see that the output is difference but i dont know why
#include <opencv2/highgui/highgui.hpp>
#include <iostream>
#include <opencv2/imgproc.hpp>
using namespace std;
using namespace cv;
cv::Mat plotHistogram(cv::Mat &image, bool cumulative = false, int histSize = 256);
int main()
{
cv::Mat img = cv::imread("\\schrott.png"); // Read the file
if (img.empty()) // Check for invalid input
{
std::cout << "Could not open or find the frame" << std::endl;
return -1;
}
cv::Mat img_gray;
cv::cvtColor(img, img_gray, cv::COLOR_BGR2GRAY); // In case img is colored
cv::namedWindow("Input Image", cv::WINDOW_AUTOSIZE); // Create a window for display.
cv::imshow("Input Image", img);
cv::Mat hist;
hist = plotHistogram(img_gray);
cv::namedWindow("Histogram", cv::WINDOW_NORMAL); // Create a window for display.
cv::imshow("Histogram", hist);
cv::waitKey(0);
}
cv::Mat plotHistogram(cv::Mat &image, bool cumulative, int histSize) {
// Create Image for Histogram
int hist_w = 1024; int hist_h = 800;
int bin_w = cvRound((double)hist_w / histSize);
cv::Mat histImage(hist_h, hist_w, CV_8UC1, Scalar(255, 255, 255));
if (image.channels() > 1) {
cerr << "plotHistogram: Please insert only gray images." << endl;
return histImage;
}
// Calculate Histogram
float range[] = { 0, 256 };
const float* histRange = { range };
cv::Mat hist;
calcHist(&image, 1, 0, Mat(), hist, 1, &histSize, &histRange);
if (cumulative) {
cv::Mat accumulatedHist = hist.clone();
for (int i = 1; i < histSize; i++) {
accumulatedHist.at<float>(i) += accumulatedHist.at<float>(i - 1);
}
hist = accumulatedHist;
}
// Normalize the result to [ 0, histImage.rows ]
normalize(hist, hist, 0, histImage.rows, NORM_MINMAX, -1, Mat());
// Draw bars
for (int i = 1; i < histSize; i++) {
cv::rectangle(histImage, Point(bin_w * (i - 1), hist_h),
Point(bin_w * (i), hist_h - cvRound(hist.at<float>(i))),
Scalar(50, 50, 50), 1);
}
return histImage; // Not really call by value, as cv::Mat only saves a pointer to the image data
}
``
Without looking at the code: the difference between a histogram and a cumulative histogram is that the cumulative histogram at index i has the value of the normal histogram at index i, plus the value of the cumulative histogram at index i - 1. There is a c++ stl algorithm that does the same, and it's called std::partial_sum.
In other words, in image processing a cumulative histogram tells you how many pixels have at most a given color value c
For example, given an array [0, 1, 2, 1, 2, 3, 2, 1, 2, 3, 4] we can plot the histogram and cumulative histogram like so:
The X axis here is the value of the array element while the Y axis is the number of times this element occurs in the array. This is a typical pattern in image processing: in a color histogram the X axis is usually the value of your color. In a 8bpp grayscale image, for example, the X axis has values in the range 0..255. The Y axis then is the number of pixels that have that specific color value.
One important property of a cumulative histogram (in contrast to a normal histogram) is that it's monotonically increasing, i.e. h[i] >= h[i - 1] where i = 1..length(h) and h is the cumulative histogram. This allows operations like histogram equalization. Since a monotonically increasing sequence is by definition also a sorted sequence, you can also perform operation on it that are only allowed on sorted sequences, like binary search.
The next step is usually to calculate a normalized cumulative histogram, which is done by dividing each value in the cumulative histogram by the number of values in your original array. For example
a = [1, 2, 3, 4]
h = hist(a) // histogram of a
ch = partial_sum(h) // cumulative histogram of a
nch = ch / length(a) // normalized, cumulative histogram of a
Another example, given an array [1, 2, 3, 4, 3, 2, 1] we can plot the histogram and cumulative histogram like so:
The X axis here is the 1 based index in the array and the Y axis is the value of that element.
Here is another figure:
And another one, explaining the same:
Starting with what the cumulative histogram is might be good. A histogram is a distribution of the number of pixels according to their intensities but if the thing in question is a cumulative histogram; we don't find counts for a single bin in the vertical axis, but rather we map that counts the cumulative number of pixel intensity values in all of the bins up to the current bin. And linear cumulative distribution or cumulative histogram is essential for some image processing algorithms e.g image equalization.
Histogram (H):
For each pixel of the image
value = Intensity(pixel)
H(value)++
end
The cumulative histogram of the H:
When you set cumulative to true; you are now calculating the cumulative histogram therefore, it is normal for the outputs to be different. In each step of the iteration, you add the previous histogram value to the cumulative histogram.
if (cumulative) {
cv::Mat accumulatedHist = hist.clone();
for (int i = 1; i < histSize; i++) {
accumulatedHist.at<float>(i) += accumulatedHist.at<float>(i - 1);
}
hist = accumulatedHist;
}
You can think of this as a trick when switching from a normal histogram to a cumulative histogram.
accumulatedHist.at<float>(i) += accumulatedHist.at<float>(i - 1);
These references might be useful to understand the basic structure
Histograms
Histogram Equalization
Cumulative Distribution Function
I want to determine the colour of an object in an image. I was able to determine the mask of the object and generated the HSV histogram of the image using the mask.
cvtColor( Frame, hsv_base, CV_BGR2HSV );
int h_bins = 50;
int s_bins = 32;
int v_bins = 10;
int histSize[] = { h_bins, s_bins, v_bins };
float h_ranges[] = { 0, 180 };
float s_ranges[] = { 0, 256 };
float v_ranges[] = { 0, 256 };
const float* ranges[] = { h_ranges, s_ranges, v_ranges };
int channels[] = { 0, 1, 2};
calcHist( &hsv_base, 1, channels, mask, hist_base, 3, histSize, ranges, true, false ); //mask is the mask of the object
Everybody shows a different method to do so.Can anyone tell me a simple method to determine the colour from the histogram?
When plotting the histogram, the X-axis serves as our “bins”. If we construct a histogram with 256 bins, then we are effectively counting the number of times each pixel value occurs.
In contrast, if we use only 2 (equally spaced) bins, then we are counting the number of times a pixel is in the range [0, 128) or [128, 255]. The number of pixels binned to the X-axis value is then plotted on the Y-axis.
Therefore, if you want to get the most common colour you need 256 bins (for a 256 bit image) and count the number of entries.
In openCV, I have a matrix of integers (a 4000x1 Mat). Each time I read different ranges of this matrix: Mat labelsForHist = labels(Range(from,to),Range(0,1));
The size of the ranges is variable. Then I convert the labelsForHist matrix to float(because calcHist doesnt accept int values!) by using:
labelsForHist.convertTo(labelsForHistFloat, CV_32F);
After this I call calcHist with these parameters:
Mat hist;
int histSize = 4000;
float range[] = { 0, 4000 } ;
int channels[] = {0};
const float* histRange = { range };
bool uniform = true; bool accumulate = false;
calcHist(&labelsForHistFloat,1,channels,Mat(),hist,1,&histSize,&histRange,uniform,accumulate);
The results are normalized by using:
normalize(hist,hist,1,0,NORM_L1,-1,Mat());
The problem is that my histograms doesn't look like what I was expecting. Any idea on what I am doing wrong or does the problem come from other part of the code (and not calculation of histograms)?
I expect this sparse histogram:
while I get this flat histogram, for same data:
The first hist was calculated in python, but I want to do the same in c++
There is a clustering process before calculating histograms, so if there is no problem with creating histograms then deffinitly the problem comes from before that in clustering part!
I have the gradients from the Sobel operator for each pixel. In my case 320x480. But how can I relate them with the orientation? For an example, I'm planning to draw an orientation map for fingerprints. So, how do I start?
Is it by dividing the gradients into blocks (example 16x24) then adding the gradients together and diving it by 384 to get the average gradients? Then from there draw a line from the center of the block using the average gradient?
Correct me if i'm wrong. Thank you.
Here are the codes that i used to find gradients
cv::Mat original_Mat=cv::imread("original.bmp", 1);
cv::Mat grad = cv::Mat::zeros(original_Mat.size(), CV_64F);
cv::Mat grad_x = cv::Mat::zeros(original_Mat.size(), CV_64F);
cv::Mat grad_y = cv::Mat::zeros(original_Mat.size(), CV_64F);
/// Gradient X
cv::Sobel(original_Mat, grad_x, CV_16S, 1, 0, 3);
/// Gradient Y
cv::Sobel(original_Mat, grad_y, CV_16S, 0, 1, 3);
short* pixelX = grad_x.ptr<short>(0);
short* pixelY = grad_y.ptr<short>(0);
int count = 0;
int min = 999999;
int max = -1;
int a=0,b=0;
for(int i = 0; i < grad_x.rows * grad_x.cols; i++)
{
double directionRAD = atan2(pixelY[i], pixelX[i]);
int directionDEG = (int)(180 + directionRAD / CV_PI * 180);
//printf("%d ",directionDEG);
if(directionDEG < min){min = directionDEG;}
if(directionDEG > max){max = directionDEG;}
if(directionDEG < 0 || directionDEG > 360)
{
cout<<"Weird gradient direction given in method: getGradients.";
}
}
There are several ways to visualize an orientation map:
As you suggested, you could draw it block-wise, but then you would have to be careful about "averaging" the directions. For example, what happens if you average the directions 0° and 180°?
More commonly, the direction is simply mapped to a grey value. This would visualize the gradient per pixel. For example as:
int v = (int)(128+directionRAD / CV_PI * 128);
(Disclaimer: not 100% sure about the 128, one of them might actually have to be a 127...
Or you could map the x and y gradient magnitudes to the rand gcomponents, respectively, ideally after normalizing the gradient vector to length 1. Assuming normX to be the normalized gradient in the x direction with values between -1 and 1:
int red = (int)((normX + 1)*127.5);
int green= (int)((normY + 1)*127.5);
Averaging depends on Sobel kernel size.
It'll be better to use CV_32FC or CV_64FC instead of CV_16S for results.
Also you can speed up your code using cv::phase method.
see my answer here: Sobel operator for gradient angle
I am using the normalise function in the code below. My understanding was that normalising the histogram would result in the bins summing to one? But when I add them all up I keep getting a result higher then one. I dont know if I am doing something wrong or have misunderstood what the function does?
//read in image
Mat img = imread("image.jpg",1);
vector<Mat> planes;
split(img, planes);
//calculate hist
MatND hist;
int nbins = 256; // hold 256 levels
int hsize[] = { nbins }; // one dimension
float range[] = { 0, 255 };
const float *ranges[] = { range };
int chnls[] = {0};
calcHist(&planes[0], 1, chnls, Mat(), hist,1,hsize,ranges);
//normalise
normalize(hist,hist,1);
float tot = 0;
for( int n = 0;n < nbins; n++ )
{
float binVal = hist.at<float>(n);
tot+=binVal;
}
cout<<tot;
Normalized array doesn't sum to 1, but square root of sum of squares of components equals 1, F.e. in vector:
It is normalized, when:
sqrt(x^2 + y^2 + z^2) = 1
*This applies for vectors
in OpenCV - histogram - normalize is described here OpenCV histogram normalize, it should be clear (after reading the specs) that it doesn't have to sum to 1