Average values of a MAT channel - c++

I want to obtain the average values of a MAT and MatND variable, just to estimate the sharpness and brightness. However, I have been facing real issues with the vague values I have been encountering. I tried my best, but am still confused. I really do not know, if am doing the right thing.
calcHist(&src_yuv,1,channels,Mat(),hist,1,histSize,ranges,true,false);
Size d = hist.size();
rows = d.height;
cols = d.width;
for(int k=0;k<hbins;k++)
{
for(int l=0;l<sbins;l++)
{
total = total + hist.at<float>(k,l);
}
}
brightness = total/(rows*cols);
Here , am trying to calculate the histogram of the luma channel of src_yuv, which is in YUV format and average the values. Am I doing it the right way? If I change the datatype within <' '>, ranging from uchar to long int, am ending up with different values, which is understandable. But I dunno which is the right data type to use. Moreover, should I loop it within hbins,sbins or rows, cols? Please help me. am stuck at this for a long time.
Laplacian(src_gray,dst,ddepth,kernel_size,scale,delta,BORDER_DEFAULT);
Size s = dst.size();
rows = s.height;
cols = s.width;
total = 0;
max = 0;
for(int k=0;k<rows;k++)
{
for(int l=0;l<cols;l++)
{
total = total + dst.at<>(k,l);
}
}
average = total/(rows*cols);
What is the exact way to compute average in the above case? Could you please help me here? I tried different datatypes, starting from in for the mat, and long int for the total and averages. Its a gray scale image, and the result in the laplacian convoluted image.

convert the input src_yuv to BGR before calcHist and you will get the desired output with the same code.
EDIT: for YUV:-
out = imread("Lena.jpg");
out.convertTo(out, CV_RGB2YCrCb);
MatND hist;
int hbins = 30, sbins = 32;
int histSize[] = {hbins, sbins};
float hranges[] = { 0, 180 };
float sranges[] = { 0, 256 };
int channels[] = {0,1,2};
const float* ranges[] = { hranges, sranges };
calcHist( &out, 1, channels, Mat(),
hist, 2, histSize, ranges,
true,
false );
Size d = hist.size();
int rows = d.height;
int cols = d.width;
float total;
float brightness;
for(int k=0;k<hbins;k++)
{
for(int l=0;l<sbins;l++)
{
total = total + hist.at<float>(k,l);
}
}
brightness = total/(rows*cols);
this gives me brightness to be 246.895

Related

Converting a Float to RGB data in opencv

I have a 2D vector of float values that I need to create an image from it.
The code that I have is as follows:
inline cv::Mat ConvertToMat(vector<vector<float>> inputData)
{
static int MAXGREY = 255;
static int MAXRANGE = 255;
int Red, Blue, Green;
float maxValue = GetMaxValue(inputData); // find max value in input data
cv::Mat output(inputData.getXSize(), inputData.getXSize(), CV_8UC3, cv::Scalar::all(0));
// if the max value is equal to or less than 0, no data in the vector to convert.
if (maxValue > 0)
{
for (int x = 0; x < inputData.size(); x++)
{
for (int y = 0; y < inputData[x].size(); y++)
{
auto Value = inputData[x][y];
Green = 0;
Red = Value * 255 / maxValue;
Blue = (maxValue - Value) * 255 / maxValue;
cv::Vec3b xyzBuffer;
xyzBuffer[0] = Blue;
xyzBuffer[1] = Red;
xyzBuffer[2] = Green;
output.at<cv::Vec3b>(x, y) = xyzBuffer;
}
}
}
return output;
}
but this method doesn't generate suitable results when there is a pixel with a very high value and a lot of pixels with small values, all small values can not be seen on the output.
for example, lets look this set of data for input:
int main()
{
vector<vector<float>> inputData =
{
{1,2,3,4,5,6,7,8,9,10},
{1.5,2.5,3.5,4.5,5.5,6.5,7.5,8.5,9.5,10.5},
{1,2,3,4,5,6,7,8,9,10},
{1.5,2.5,3.5,4.5,5.5,6.5,7.5,8.5,9.5,10.5},
{1,2,3,4,2000,6,7,8,9,10},
{1.5,2.5,3.5,4.5,5.5,6.5,7.5,8.5,9.5,10.5},
{1,2,3,4,5,6,7,8,9,10},
{1.5,2.5,3.5,4.5,5.5,6.5,7.5,8.5,9.5,10.5},
{1,2,3,4,5,6,7,8,9,10},
{1.5,2.5,3.5,4.5,5.5,6.5,7.5,8.5,9.5,10.5}
};
cv::Mat image=ConvertToMat(inputData);
cv::imwrite("c://tmp//myimage.jpg", image);
return 0;
}
The generated out is as follow (the value of each pixel is shown on the pixel):
Since we have 3X byte data for colour, we should have enough dynamic range to show the data for each pixel in a different colour. but the above algorithm converts the value of 1 and 2 and 3 into the same colour (254,0,0).
How can I convert a float into three different colours so I can see all pixels with a different colour suitable for visual inspection (so each pixel which is near to the other has similar colour but not the same colour)?

calcHist with multiple channels in openCV

I'm trying to compute a 3D 2x2x2 RGB histogram with openCV with cv::calcHist. However I don't understand how calcHist works with multiple channels. I want it to select one of two red layer of bins i, one of two green layer of bins j and one of two blue layer of bins k and store the result into the corresponding triple-indexed bin bins[i][j][k]. But that's not what it's doing.
The following loop works fine
int bin_index(const float x, unsigned int nbins)
{
return std::min(static_cast<unsigned int>(x*nbins), nbins - 1);
}
//...
for(cv::Point pixel_idx : all possible pixel positions)
{
int red_idx = bin_index(image(pixel_idx)[0], number_of_red_bins);
int green_idx = bin_index(image(pixel_idx)[0], number_of_green_bins);
int blue_idx = bin_index(image(pixel_idx)[0], number_of_blue_bins);
histogram[blue_idx * number_of_green_bins * number_of_red_bins
+ green_idx * number_of_red_bins + red_idx] += 1;
}
But the following usage of cv::calcHist yield a different result
cv::Mat block = image(rect of the patch);
const int channels[] = {0, 1, 2};
const int histSize[] = {number_of_red_bins, number_of_green_bins, number_of_blue_bins};
constexpr int histSizeDims = sizeof(histSize) / sizeof(const int);
const float range[] {0.0f, 1.001f};
const float* histRanges[] = {range, range, range};
cv::Mat result;
cv::calcHist(
&block,
1,
channels,
cv::Mat(),
result,
histSizeDims,
histSize,
histRanges,
true, false);
Can you tell me how how calcHist behaves with multi-dimentional histograms ? Thank you for your help !

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.

Finding HSV Thresholds Via Histograms with OpenCV

I'm trying to write a method that will find the proper threshold values in HSV space for an object placed at the center of the screen. These values are used for an object tracking algorithm. I've tested that piece of code with hand coded threshold values and it works well. The idea behind the method is that it should calculate the histograms for each of the channels and then return the 5th and 95th percentile for each to be used as the threshold values. (credit: How to find RGB/HSV color parameters for color tracking?) The image being passed is a picture of the object to be tracked (which is set by the user before the whole process begins. Here is the code
std::vector<cv::Scalar> HSV_Threshold_Determiner::Get_Threshold_Values(const cv::Mat& image)
{
cv::Mat inputImage;
cv::cvtColor(image, inputImage, CV_BGR2HSV);
std::vector<cv::Mat> bgrPlanes;
cv::split(inputImage, bgrPlanes);
cv::Mat hHist, sHist, vHist;
int hMax = 180, svMax = 256;
float hRanges[] = { 0, (float)hMax };
const float* hRange = { hRanges };
float svRanges[] = { 0, (float)svMax };
const float* svRange = { svRanges };
//float sRanges[] = { 0, 256 };
cv::calcHist(&bgrPlanes[0], 1, 0, cv::Mat(), hHist, 1, &hMax, &hRange);
cv::calcHist(&bgrPlanes[1], 1, 0, cv::Mat(), sHist, 1, &svMax, &svRange);
cv::calcHist(&bgrPlanes[2], 1, 0, cv::Mat(), vHist, 1, &svMax, &svRange);
int totalEntries = image.cols * image.rows;
int fiveCutoff = (int)(totalEntries * .05);
int ninetyFiveCutoff = (int)(totalEntries * .95);
float hTotal = 0, sTotal = 0, vTotal = 0;
bool hMinFound = false, hMaxFound = false, sMinFound = false, sMaxFound = false,
vMinFound = false, vMaxFound = false;
cv::Scalar hThresholds;
cv::Scalar sThresholds;
cv::Scalar vThresholds;
for(int i = 0; i < vHist.rows; ++i)
{
if(i < hHist.rows)
{
hTotal += hHist.at<float>(i, 0);
if(hTotal >= fiveCutoff && !hMinFound)
{
hThresholds.val[0] = i;
hMinFound = true;
}
else if(hTotal>= ninetyFiveCutoff && !hMaxFound)
{
hThresholds.val[1] = i;
hMaxFound = true;
}
}
sTotal += sHist.at<float>(i, 0);
vTotal += vHist.at<float>(i, 0);
if(sTotal >= fiveCutoff && !sMinFound)
{
sThresholds.val[0] = i;
sMinFound = true;
}
else if(sTotal >= ninetyFiveCutoff && !sMaxFound)
{
sThresholds.val[1] = i;
sMaxFound = true;
}
if(vTotal >= fiveCutoff && !vMinFound)
{
vThresholds.val[0] = i;
vMinFound = true;
}
else if(vTotal >= ninetyFiveCutoff && !vMaxFound)
{
vThresholds.val[1] = i;
vMaxFound = true;
}
if(vMaxFound && sMaxFound && hMaxFound)
{
break;
}
}
std::vector<cv::Scalar> returnVect;
returnVect.push_back(hThresholds);
returnVect.push_back(sThresholds);
returnVect.push_back(vThresholds);
return returnVect;
}
What I am trying to do is sum up the number of entries in each bucket until I get to a number that is greater than or equal to five percent and ninety-five percent of the total. Unfortunately the numbers I get are never close to the ones I get if I do the thresholding by hand.
Mat img = ... // from camera or some other source
// STEP 1: learning phase
Mat hsv, imgThreshed, processed, denoised;
cv::GaussianBlur(img, denoised, cv::Size(5,5), 2, 2); // remove noise
cv::cvtColor(denoised, hsv, CV_BGR2HSV);
// lets say we picked manually a region of 100x100 px with the interested color/object using mouse
cv::Mat roi = hsv (cv::Range(mousex-50, mousey+50), cv::Range(mousex-50, mousey+50));
// must split all channels to get Hue only
std::vector<cv::Mat> hsvPlanes;
cv::split(roi, hsvPlanes);
// compute statistics for Hue value
cv::Scalar mean, stddev;
cv::meanStdDev(hsvPlanes[0], mean, stddev);
// ensure we get 95% of all valid Hue samples (statistics 3*sigma rule)
float minHue = mean[0] - stddev[0]*3;
float maxHue = mean[0] + stddev[0]*3;
// STEP 2: detection phase
cv::inRange(hsvPlanes[0], cv::Scalar(minHue), cv::Scalar(maxHue), imgThreshed);
imshow("thresholded", imgThreshed);
cv_erode(imgThreshed, processed, 5); // minimizes noise
cv_dilate(processed, processed, 20); // maximize left regions
imshow("final", processed);
//STEP 3: do some blob/contour detection on processed image & find maximum blob/region, etc ...
A much simpler solution - just calculate mean & std. deviation for a region of interest, i.e. containing the Hue value.
Since Hue is the most stable component in the image, the other components saturation & value should be discarded as they vary too much. However you can still compute mean for them if needed.

accessing image pixels as float array

I want to access image pixels as float array in opencv. Ive done the following:
Mat input = imread("Lena.jpg",CV_LOAD_IMAGE_GRAYSCALE);
int height = input.rows;
int width = input.cols;
Mat out;
input.convertTo(input, CV_32FC1);
copyMakeBorder(input, input, 3, 3, 3, 3, 0);
out = Mat(height, width, input.type());
float *outdata = (float*)out.data;
float *indata = (float*)input.data;
for(int j = 0; j < height; j++){
for(int i =0; i < width; i++){
outdata[j*width + i] = indata[(j* width + i)];
}
}
normalize(out, out,0,255,NORM_MINMAX,CV_8UC1);
imshow("output", out);
waitKey();
This should return the original image in "out", however, I'm getting some weird image. Can anyone explain whats wrong with the code. I think i need to use some step size (widthStep). Thanks.
the line
copyMakeBorder(input, input, 3, 3, 3, 3, 0);
changes the dimensions of input, it adds 6 rows and 6 columns to the image. That means your height and width variables are holding the wrong values when you define out and try to loop over the values on input.
if you change the order to
copyMakeBorder(input, input, 3, 3, 3, 3, 0);
int height = input.rows;
int width = input.cols;
it should work fine.
Some ideas:
Something like outdata[j*width + i] is a more standard pattern for this sort of thing.
According to the opencv documentation, there is a templated Mat::at(int y, int x) method that allows you to access individual elements of a matrix.
float f = input.at<float>(0, 0);
Note that this requires that your underlying matrix type is float -- it won't do a conversion for you.
Alternatively, you could access the data row-by-row, as in this example that sums up the positive elements of a matrix M of type double:
double sum=0;
for(int i = 0; i < M.rows; i++)
{
const double* Mi = M.ptr<double>(i);
for(int j = 0; j < M.cols; j++)
sum += std::max(Mi[j], 0.);
}
If none of these work, I'd suggest creating a small matrix with known values (e.g. a 2x2 matrix with 1 black pixel and 3 white pixels) and use that to help debug your code.
To really make it apparent what the problem is, imagine a 16 by 16 image. Now think of pixel number 17 in the linear representation.
17 is a prime number. There is no j*i that will index your source image at pixel 17 if the row or column width is 16. Thus elements like 17, 19, 23 and so on will be uninitialized or at best 0, resulting in a "weird" output.
How about pixel 8 in the linear representation? that one in contrast will get hit by your loop four times, i.e. by 1x8, 2x4, 4x2, and 8x1!
The indexing #NateKohl presents in his answer will fix it since he multiplies a row position by the length of the row and then simply walks along the columns.
You can try this loop...
for(int row=0;row<height;row++)
{
for(int col=0;col<width;col++)
{
float float_data = input.at<float>(row,col);
// do some processing with value of float_data
out.at<float>(row,col) = float_data;
}
}
Is there a need to cast the uchar pointers of input and out Mats to float pointers?