I've been going over my code for a few hours now and I'm not sure why this contrast algo isn't working.
Following this guide I've used the small algorithm given on the post. However I did mine using HSI color scheme because my pictures need to be in color. I have noted the changes for HSI in the post however they didn't give me a step by step on exactly how to do it. Also they're using pillow, whereas I'm using Cimg.
My code compiles and runs with no errors. But the result is a very dark image.
I was hoping for an output similar to what I get if increasing contrast using camera raw filter in photoshop. This is the a result of maxing the photoshop contrast slider:
This is the tail of the modified intensity values and the min max values:
old Intensity 0.422222
new Intensity 0.313531
old Intensity 0.437909
new Intensity 0.353135
old Intensity 0.437909
new Intensity 0.353135
old Intensity 0.436601
new Intensity 0.349835
old Intensity 0.439216
new Intensity 0.356436
old Intensity 0.443137
new Intensity 0.366337
old Intensity 0.45098
new Intensity 0.386139
old Intensity 0.458824
new Intensity 0.405941
old Intensity 0.461438
new Intensity 0.412541
min 0.298039
max 0.694118
Hope someone can help, thanks.
#include <iostream>
#include "CImg.h"
int main() {
cimg_library::CImg<float> lenaCondec("./colors/lena_condec.jpeg");
int width = lenaCondec.width();
int height = lenaCondec.height();
// enhancing contrast
float minIntensity = 1.0f;
float maxIntensity = 0.0f;
cimg_library::CImg<float> imgBuffer = lenaCondec.get_RGBtoHSI();
for (int row = 0; row < height; row++)
for (int col = 0; col < width; col++) {
const auto I = imgBuffer(col, row, 0, 2);
minIntensity = std::min((float)I, minIntensity);
maxIntensity = std::max((float)I, maxIntensity);
}
for (int row = 0; row < height; row++)
for (int col = 0; col < width; col++) {
auto I = imgBuffer(col, row, 0, 2);
const auto newIntensity = (((float)I - minIntensity) / (maxIntensity - minIntensity));
std::cout << "old Intensity " << (float)I << std::endl;
imgBuffer(col, row, 0, 2) = newIntensity;
I = imgBuffer(col, row, 0, 2);
std::cout << "new Intensity " << (float)I << std::endl;
}
std::cout << "min " << minIntensity << std::endl;
std::cout << "max " << maxIntensity << std::endl;
cimg_library::CImg<float> outputImg = imgBuffer.get_HSItoRGB();
// Debugging
outputImg.save_jpeg("./colors/output-image.jpeg");
std::getchar();
return 0;
}
I have a repo for this here. Make sure you're in the "so-question" branch.
Note: I modified line 389 of CImg.h from #include <X11/Xlib.h> -> #include "X11/Xlib.h"
The algorithm above scales the image into [0, 1] range.
Namely the pixels with the lowest values will be mapped to 0 and the pixels with highest values will be mapped to 1.
You need to apply thin on RGB image which its values are in the range [0, 1]. You need to apply it per channel.
I think there may be an issue with the built-in JPEG implementation in CImg. I found that your code works fine if you save the output file as a PNG instead of a JPEG.
Alternatively you can force CImg to use the IJPEG implementation on your Mac with:
clang++ $(pkg-config --cflags --libs libjpeg) -std=c++17 -Dcimg_use_jpeg -lm -lpthread -o "main" "main.cpp"
As a pre-requisite, you may need to install pkkconfig and jpeg with homebrew:
brew install jpeg pkgconfig
Note also that, as long as you don't want to use CImg display(), you can avoid needing to put all the paths and switches for X11 on your compilation command by changing your compilation command to this:
clang++ -Dcimg_display=0 ...
As you mentioned you might consider other ways of stretching the contrast, I thought I'd add another option where you can do it in RGB colourspace. If you find the minimum and maximum of the Red channel and stretch the reds, and then do likewise for the other channels, you will introduce a colour cast. So, an alternative is to find the minimum of all channels and maximum of all channels and then stretch the channels in concert by the same amount.
Effectively, you are stretching the RGB histogram until any of the channels hits 0 or 255. My C++ is a bit clumsy, but it looks something like this:
#include <iostream>
#include "CImg.h"
int main() {
cimg_library::CImg<unsigned char> img("lena.png");
int width = img.width();
int height = img.height();
// Find min and max RGB values for whole image
unsigned char RGBmin = 255;
unsigned char RGBmax = 0;
for (int row = 0; row < height; row++) {
for (int col = 0; col < width; col++) {
const auto R = img(col, row, 0, 0);
const auto G = img(col, row, 0, 1);
const auto B = img(col, row, 0, 2);
RGBmin = std::min({R,G,B,RGBmin});
RGBmax = std::max({R,G,B,RGBmax});
}
}
std::cout << "RGBmin=" << int(RGBmin) << ", RGBmax=" << int(RGBmax) << std::endl;
// Stretch contrast equally for all channels
for (int row = 0; row < height; row++) {
for (int col = 0; col < width; col++) {
for (int chan = 0; chan <=3; chan++) {
const auto x = img(col, row, 0, chan);
const auto newVal = 255*((float)x - RGBmin) / (RGBmax - RGBmin);
img(col, row, 0, chan) = (unsigned char)newVal;
}
}
}
// Debugging
img.save("result2.png");
}
Related
I am doing a homework where we need to write a function which gets an image and a kernel and we have to calculate the 2d spatial convolution.
Using a gaussian kernel I get the expected result (a blurred image) but if I use instead for example an edge detection kernel (taken from here) I see that something isn't working properly (the image becomes very greyish).
I guess the problem is either the border handling, which should be a zero-padding but I am not totally sure if implemented it correctly or the normalization at the end.
Is there a way to display a float image (e.g. one pixel of the float has a value for 25000), because I think it always gets capped at 255 (white) if I don't use the normalization.
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <iostream>
int main(int argc, char *argv[])
{
cv::Mat img = cv::imread("orig.jpg",0); // load image as grayscale
img.convertTo(img,CV_32FC1); // convert to float
cv::Mat_<float> output(img.rows,img.cols); // create new mat with same size as source image
output = 0;
// creating a kernel (here Gaussian blur)
cv::Mat_<float> kernel(5,5);
kernel << 1,4,6,4,1,4,16,24,16,4,6,24,36,24,6,4,16,24,16,4,1,4,6,4,1;
int kCenterX = kernel.cols/2;
int kCenterY = kernel.rows/2;
for (int i = 0; i < img.rows; i++){ // for every row in image
for (int j = 0; j < img.cols; j++){ // for every column in image
for (int m = 0; m < kernel.rows; m++){ // for every row of kernel
int mm = kernel.rows - 1 -m; // row index of flipped kernel
for (int n = 0; n < kernel.cols; n++){ // for every column of kernel
int nn = kernel.cols - 1 -n; // column index of flipped kernel
// index for border handling
int ii = i + (m - kCenterY);
int jj = j + (n - kCenterX);
// checking if sample is still in bound of input image
// and if not, treat those pixels as 0 (because they won't get added to sum)
if (ii >= 0 && ii < img.rows && jj >= 0 && jj < img.cols)
output.at<float>(i,j) += img.at<float>(ii,jj) * kernel.at<float>(mm,nn);
}
}
}
}
// normalize input and output image (might be wrong, but I don't know how else I can see float images
cv::normalize(output, output, 0, 1, cv::NORM_MINMAX);
cv::normalize(img, img, 0, 1, cv::NORM_MINMAX);
// display images
cv::imshow("Original", img);
cv::imshow("Convolution", output);
cv::waitKey(0);
return 0;
}
I want calculate the mean and standard deviations for a histogram of a HSV image but I only want to do this histogram and calculations for the V channel.
I have been reading examples on how to do this for a set of channels and have tried these approaches but I am getting confused over whether my approach for initially creating the histogram is correct or not for just one channel because the program keeps crashing when i try to execute it.
Here is what I have at the moment (The variable test is a cv::Mat image and this can be any image you wish to use to recreate the issue). I have probably missed something obvious and the for loop might not be correct in terms of the range of values but I haven't done this in C++ before.
cv::cvtColor(test, test, CV_BGR2HSV);
int v_bins = 50;
int histSize[] = { v_bins };
cv::MatND hist;
float v_ranges[] = { 0, 255};
cv::vector<cv::Mat> channel(3);
split(test, channel);
const float* ranges[] = { v_ranges };
int channels[] = {0};
cv::calcHist(&channel[2], 1, channels, cv::Mat(), hist, 1, histSize, ranges, true, false); //histogram calculation
float mean=0;
float rows= hist.size().height;
float cols = hist.size().width;
for (int v = 0; v < v_bins; v++)
{
std::cout << hist.at<float>(v, v) << std::endl;;
mean = mean + hist.at<float>(v);
}
mean = mean / (rows*cols);
std::cout << mean<< std::endl;;
You can simply use cv::meanStdDev, that calculates a mean and standard deviation of array elements.
Note that both mean and stddev arguments are cv::Scalar, so you need to do mean[0] and stddev[0] to get the double values of your single channel array hist.
This code will clarify it's usage:
#include <opencv2\opencv.hpp>
#include <iostream>
int main()
{
cv::Mat test = cv::imread("path_to_image");
cv::cvtColor(test, test, CV_BGR2HSV);
int v_bins = 50;
int histSize[] = { v_bins };
cv::MatND hist;
float v_ranges[] = { 0, 255 };
cv::vector<cv::Mat> channel(3);
split(test, channel);
const float* ranges[] = { v_ranges };
int channels[] = { 0 };
cv::calcHist(&channel[2], 1, channels, cv::Mat(), hist, 1, histSize, ranges, true, false); //histogram calculation
cv::Scalar mean, stddev;
cv::meanStdDev(hist, mean, stddev);
std::cout << "Mean: " << mean[0] << " StdDev: " << stddev[0] << std::endl;
return 0;
}
UPDATE
You can compute the mean and the standard deviation by their definition:
double dmean = 0.0;
double dstddev = 0.0;
// Mean standard algorithm
for (int i = 0; i < v_bins; ++i)
{
dmean += hist.at<float>(i);
}
dmean /= v_bins;
// Standard deviation standard algorithm
std::vector<double> var(v_bins);
for (int i = 0; i < v_bins; ++i)
{
var[i] = (dmean - hist.at<float>(i)) * (dmean - hist.at<float>(i));
}
for (int i = 0; i < v_bins; ++i)
{
dstddev += var[i];
}
dstddev = sqrt(dstddev / v_bins);
std::cout << "Mean: " << dmean << " StdDev: " << dstddev << std::endl;
and you'll get the same values as OpenCV meanStdDev.
Be careful about calculating statistics on a histogram. If you just run meanStdDev, you'll get the mean and stdev of the bin values. That doesn't tell you an awful lot.
Probably what you want is the mean and stdev intensity.
So, if you want to derive the image mean and standard deviation from a histogram (or set of histograms), then you can use the following code:
// assume histogram is of type cv::Mat and comes from cv::calcHist
double s = 0;
double total_hist = 0;
for(int i=0; i < histogram.total(); ++i){
s += histogram.at<float>(i) * (i + 0.5); // bin centre
total_hist += histogram.at<float>(i);
}
double mean = s / total_hist;
double t = 0;
for(int i=0; i < histogram.total(); ++i){
double x = (i - mean);
t += histogram.at<float>(i)*x*x;
}
double stdev = std::sqrt(t / total_hist);
From the definitions of the mean:
mean = sum(x * p(x)) // expectation
std = sqrt(sum( p(x)*(x - mean)**2 ) // sqrt(variance)
The mean is the expectation value for x. So histogram[x]/sum(histogram) gives you p(x). The definition of standard deviation is similar and comes from the variance. The numbers are slightly simpler because pixels can only take integer values and are unit spaced.
Note this is also useful if you want to calculate normalisation statistics for a batch of images using the accumulate option.
Adapted from: How to calculate the standard deviation from a histogram? (Python, Matplotlib)
I'm trying to get a Kinect to find a red shape and seem to be having difficulty comparing the actual RGBA values from each pixel. The code should be pretty self-explanatory. The text output file is just for me to get a quick representation of what's getting assigned to the array for future calculations.
void CColorBasics::getRGB (BYTE* bitPointer, LONG width, LONG height)
{
int red, green, blue, alpha;
int arr[640][480];
ofstream myfile;
myfile.open ("asdf.txt");
for (int i=0; i<height; i ++)
{
for (int ii=0; ii<width; ii = ii = ii+4)
{
blue = (int)bitPointer[ii];
green = (int)bitPointer[ii+1];
red = (int)bitPointer[ii+2];
alpha = (int)bitPointer[ii+3];
//calculate differences between BG and R and store result in an array
if (red > (green+blue) && red >= 150)
{
arr[i][ii] = 1;
myfile << "1";
}
else
{
arr[i][ii] = 0;
myfile << "0";
}
}
myfile << "\n";
}
myfile.close();
}
Instead of getting a pattern that resembles anything close to what the RGBA sees, I get some semi-random stuff that seems to have to do with how much red is in the picture, but certainly isn't formatted correctly. Essentially, if a pixel is "significantly red", I would like a '1' to be stored in that pixel's location in the array[][] - otherwise, store a '0' in that spot. Tips would definitely be appreciated!
I see there are similar questions to this but don't quiet answer what I am asking so here is my question.
In C++ with OpenCV I run the code I will provide below and it returns an average pixel value of 6.32. However, when I open the image and use the mean function in MATLAB it returns an average pixel intensity of approximately 6.92ish. As you can see I convert the OpenCV values to double to try to ease this issue and have found that openCV loads the image as a set of integers whereas MATLAB loads the image as decimal values that are approximately but not quite the same obviously as the integers. So my question is, being new to coding, which is correct? I'm assuming MATLAB is returning more accurate values and if that is the case I would like to know if there is a way to load the images in the same fashion to avoid the discrepancy.
Thank you, Code below
Mat img = imread("Cells2.tif");
cv::cvtColor(img, img, CV_BGR2GRAY);
cv::imshow("stuff",img);
Mat dst;
if(img.channels() == 3)
{
img.convertTo(dst, CV_64FC1);
}
else if (img.channels() == 1)
{
img.convertTo(dst, CV_64FC1);
}
cv::imshow("output",dst/255);
int NumPixels = img.total();
double avg;
double c = 0;
double std;
for(int y = 0; y < dst.cols; y++)
{
for(int x = 0; x < dst.rows; x++)
{
c+=dst.at<double>(x,y)*255;
}
}
avg = c/NumPixels;
cout << "asfa = " << c << endl;
double deviation;
double var;
double z = 0;
double q;
//for(int a = 0; a<= img.cols; a++)
for(int y = 0; y< dst.cols; y++)
{
//for(int b = 0; b<= dst.rows; b++)
for(int x = 0; x< dst.rows; x++)
{
q=dst.at<double>(x,y);
deviation = q - avg;
z = z + pow(deviation,2);
//cout << "q = " << q << endl;
}
}
var = z/(NumPixels);
std = sqrt(var);
cv::Scalar avgPixel = cv::mean(dst);
cout << "Avg Value = " << avg << endl;
cout << "StdDev = " << std << endl;
cout << "AvgPixel =" << avgPixel;
cvWaitKey(0);
return 0;
}
According to your comment, the image seems to be stored with a 16-bit depth. MATLAB loads the TIFF image as is, while by default OpenCV will load images as 8-bit. This might explain the difference in precision that you are seeing.
Use the following to open the image in OpenCV:
cv::Mat img = cv::imread("file.tif", cv::IMREAD_ANYDEPTH|cv::IMREAD_ANYCOLOR);
In MATLAB, it's simply:
img = imread('file.tif');
Next you need to be aware of the data type you are working with. In OpenCV its CV_16U, in MATLAB its uint16. Therefore you need to convert types accordingly.
For example, in MATLAB:
img2 = double(img) ./ double(intmax('uint16'));
would convert it to a double image with values in the range [0,1]
When you load the image, you must use similar methods in both environments (MATLAB and OpenCV) to avoid possible conversions which may be done by default in either environment.
You are converting the image if certain conditions are met, this can change some color values while MATLAB can choose to not convert the image but use the raw image
colors are mostly represented in hex format with popular implementations in the format of 0xAARRGGBB or 0xRRGGBBAA, so 32 bit integers will do (unsigned/signed doesn't matter, the hex value is still the same), create a 64 bit variable, add all the 32 bit variables together and then divide by the amount of pixels, this will get you a quite accurate result (for images up to 16384 by 16384 pixels (where a 32 bit value is representing the color of one pixel), if larger, then a 64 bit integer will not be enough).
long long total = 0;
long long divisor = image.width * image.height;
for(int x = 0; x < image.width; ++x)
{
for(int y = 0; x < image.height; ++x)
{
total += image.at(x,y).color;
}
}
double avg = total / divisor;
std::cout << "Average color value: " << avg << std::endl;
Not sure what difficulty you are having with mean value in Matlab versus OpenCV. If I understand your question correctly, your goal is to implement Matlab's mean(image(:)) in OpenCV. For example in Matlab you do the following:
>> image = imread('sheep.jpg')
>> avg = mean(image(:))
ans =
119.8210
Here's how you do the same in OpenCV:
Mat image = imread("sheep.jpg");
Scalar avg_pixel;
avg_pixel = mean(image);
float avg = 0;
cout << "mean pixel (RGB): " << avg_pixel << endl;
for(int i; i<image.channels(); ++i) {
avg = avg + avg_pixel[i];
}
avg = avg/image.channels();
cout << "mean, that's equivalent to mean(image(:)) in Matlab: " << avg << endl;
OpenCV console output:
mean pixel (RGB): [77.4377, 154.43, 127.596, 0]
mean, that's equivalent to mean(image(:)) in Matlab: 119.821
So the results are the same in Matlab and OpenCV.
Follow up
Found some problems in your code.
OpenCV stores data differently from Matlab. Look at this answer for a rough explanation on how to access a pixel in OpenCV. For example:
// NOT a correct way to access a pixel in CV_F32C3 type image
double pixel = image.at<double>(x,y);
//The correct way (where the pixel value is stored in a vector)
// Note that Vec3d is defined as: typedef Vec<double, 3> Vec3d;
Vec3d pixel = image.at<Vec3d>(x, y);
Another error I found
if(img.channels() == 3)
{
img.convertTo(dst, CV_64FC1); //should be CV_64FC3, instead of CV_64FC1
}
Accessing Mat elements may be confusing. I suggest getting a book on OpenCV to get started, for example this one, and read OpenCV tutorials and documentation. Hope this helps.
I am confused about the use of number of channels.
Which one is correct of the following?
// roi is the image matrix
for(int i = 0; i < roi.rows; i++)
{
for(int j = 0; j < roi.cols; j+=roi.channels())
{
int b = roi.at<cv::Vec3b>(i,j)[0];
int g = roi.at<cv::Vec3b>(i,j)[1];
int r = roi.at<cv::Vec3b>(i,j)[2];
cout << r << " " << g << " " << b << endl ;
}
}
Or,
for(int i = 0; i < roi.rows; i++)
{
for(int j = 0; j < roi.cols; j++)
{
int b = roi.at<cv::Vec3b>(i,j)[0];
int g = roi.at<cv::Vec3b>(i,j)[1];
int r = roi.at<cv::Vec3b>(i,j)[2];
cout << r << " " << g << " " << b << endl ;
}
}
the second one is correct,
the rows and cols inside the Mat represents the number of pixels,
while the channel has nothing to do with the rows and cols number.
and CV use BGR by default, so assuming the Mat is not converted to RGB then the code is correct
reference, personal experience, OpenCV docs
A quicker way to get color components from an image is to have the image represented as an IplImage structure and then make use of the pixel size and number of channels to iterate through it using pointer arithmetic.
For example, if you know that your image is a 3-channel image with 1 byte per pixel and its format is BGR (the default in OpenCV), the following code will get access to its components:
(In the following code, img is of type IplImage.)
for (int y = 0; y < img->height; y++) {
for(int x = 0; x < img->width; x++) {
uchar *blue = ((uchar*)(img->imageData + img->widthStep*y))[x*3];
uchar *green = ((uchar*)(img->imageData + img->widthStep*y))[x*3+1];
uchar *red = ((uchar*)(img->imageData + img->widthStep*y))[x*3+2];
}
}
For a more flexible approach, you can use the CV_IMAGE_ELEM macro defined in types_c.h:
/* get reference to pixel at (col,row),
for multi-channel images (col) should be multiplied by number of channels */
#define CV_IMAGE_ELEM( image, elemtype, row, col ) \
(((elemtype*)((image)->imageData + (image)->widthStep*(row)))[(col)])
I guess the 2nd one is correct, nevertheless it is very time consuming to get the data like that.
A quicker method would be to use the IplImage* data structure and increment the address pointed with the size of the data contained in roi...