Save exr/pfm to bitmap CImg - c++

I am trying to convert some bitmap files into custom images (exr, pfm, whatever), and after that, back to bitmap:
CImg<float> image(_T("D:\\Temp\\test.bmp"));
image.normalize(0.0, 1.0);
image.save_exr(_T("D:\\Temp\\test.exr"));
and goes fine (same for .pfm file), I mean the exr file is ok, same for pfm file.
But when this exr, or pfm file I trying to convert back to bitmap:
CImg<float> image;
image.load_exr(_T("D:\\Temp\\test.exr")); // image.load_pfm(_T("D:\\Tempx\\test.pfm"));
image.save_bmp(_T("D:\\Temp\\test2.bmp"));
the result, test2.bmp is black. Complete. Why ? What I am doing wrong ?

Some image formats support saving as float, but most formats save as unsigned 8 bit integer (or uint8), meaning normal image values are from 0 to 255. If you try to save an array that is made up of floats from 0 to 1 into a format that does not support floats, your values will most likely be converted to integers. When you display your image with most image-viewing software, it'll appear entirely black since 0 is black and 1 is almost black.
Most likely when you save your image to bitmap it is trying to convert the values to uint8 but not scaling properly. You can fix this by multiplying normalized values between 0 and 1 by 255. img = int(img*255) or using numpy img = (img*255).astype(np.uint8).
It is also possible that somehow your save function is able to preserve floating point values in the bitmap format. However your image viewing software might not know how to view/display a float image. Perhaps use some imshow function (matplotlib.pyplot can easily display floating point grayscale arrays) between each line of code to check if the arrays are consistent with what you expect them to be.

Related

imwrite in opencv gives a black/white image

I wrote a code for watershed segmentation in C API. Now I am converting all those into C++. so, cvsaveimage becomes imwrite. But when I use imwrite ,all i get is a black image.
this is the code:-
Mat img8bit;
Mat img0;
img0 = imread("source.png", 1);
Mat wshed(img0.size(), CV_32S);
wshed.setTo(cv::Scalar::all(0));
////after performing watershed segmentation and
// displaying the watershed image from wshed//
wshed.convertTo(img8bit, CV_32FC3, 255.0);
imwrite("Watershed.png", img8bit);
The original image that I want to save is in wshed. I saw suggestions from the net that we need to convert it to 16 bit or higher so that the imwrite saves it right. Like you see,I tried that. But the wshed image is being displayed correctly when using imshow.The img0 is grey image/black and white while the wshed image is coloured. any help on this?
Edit- I changed the 4th line to
Mat wshed(img0.size(), CV_32FC3);
When calling Mat::convertTo() with a scalar (255 in your case), the values of every matrix item will be multiplied by this scalar value. This will cause all most every result pixel values exceed 255 (i.e. white pixels) except those of 0s where they remain 0 (i.e. black pixels). This is why you will get the black-white pixel in the end.
To make it work, simply change it to:
wshed.convertTo(img8bit, CV_32FC3);
You said:
The original image that I want to save is in wshed. I saw suggestions
from the net that we need to convert it to 16 bit or higher so that
the imwrite saves it right.
If saving the image does not work you should keep in mind that the image data has to be either 8-Bits or 16-Bit unsigned when using the imwrite Function, not 16-Bits or higher.
This is stated in the documentation:
The function imwrite saves the image to the specified file. The image
format is chosen based on the filename extension (see imread() for the
list of extensions). Only 8-bit (or 16-bit unsigned (CV_16U) in case
of PNG, JPEG 2000, and TIFF) single-channel or 3-channel (with ‘BGR’
channel order) images can be saved using this function. If the format,
depth or channel order is different, use Mat::convertTo() , and
cvtColor() to convert it before saving. Or, use the universal
FileStorage I/O functions to save the image to XML or YAML format.

OpenCV convertTo()

I came across this code:
image.convertTo(temp_image,CV_16SC3);
I saw the description of the convertTo() function from here, but what confuses me is image. How can we read the above code? What would be the relation between image and temp_image?
Thanks.
The other answers here are correct, but lack some details. Let me try.
image.convertTo(temp_image,CV_16SC3);
You have a source image image, and a destination image temp_image. You didn't specify the type of image, but probably is CV_8UC3 or CV_32FC3, i.e. a 3 channel image (since convertTo doesn't change the number of channels), where each channel has depth 8 bit (unsigned char, CV_8UC3) or 32 bit (float, CV_32FC3).
This line of code will change the depth of each channel, so that temp_image has each channel of depth 16 bit (short). Specifically it's a signed short, since the type specifier has the S: CV_16SC3.
Note that if you are narrowing down the depth, as in the case from float to signed short, then saturate_cast will make sure that all the values in temp_image will be in the correct range, i.e. in [–32768, 32767] for signed short.
Why you need to change the depth of an image?
Some OpenCV functions require input images with a specific depth.
You need a matrix to contain a different range of values. E.g. if you need to sum (or subtract) some images CV_8UC3 (tipically BGR images), you'd better store the result in a CV_16SC3 or you'll probably get wrong results due to saturations, since the range for CV_8U images is in [0,255]
You read with imread, or want to store with imwrite images with 16bit depth. This are usually used (AFAIK) in medical or graphics application to allow a wider range of colors. However, most monitors do not support 16bit image visualization.
There may be other cases, let me know if I miss the one important to you.
An image is a matrix of pixel information (i.e. a 1080p image will be a 1,920 × 1,080 matrix where each entry contains rbg values for that pixel). All you are doing is reformatting that matrix (each pixel entry, iteratively) into a new type (CV_16SC3) so it can be read by different programs.
The temp_image is a new matrix of pixel information based off of image formatted into CV_16SC3.
The first one is a source, the second one - destination. So, it takes image, converts it into type CV_16SC3 and stores in temp_image.

cannot read correct pgm pixel values

I have a really weird error,
so I'm trying to read a pgm image by loading its pixel values into an array, I was able to correctly read in its version, height, width, and maximum possible pixel value. However, when I start reading the pixel values, I always get 0. (I know it's not zero because I can read it using imread in matlab, but have to implement it in c++, plus I couldn't use the opencv library so..)
And besides, when I read the pgm file in like NotePad++, the first few lines are good representing the information about this image ,how ever, the actual pixel values are not readable. I'm wondering if I need some sort of parsing to read a pgm image? Its version is p5.
Thanks!
You must have an assignment to solve as there is no sane reason implementing a PGM reader otherwise.
There are two different PGM formats: ASCII and binary. You seem to expect an ASCII PGM but the one you have is binary.
Have a look at the specs: http://netpbm.sourceforge.net/doc/pgm.html
It says:
/1. A "magic number" for identifying the file type. A pgm image's
magic number is the two characters "P5".
[…]
/9. A raster of Height rows, in order from top to bottom. Each row
consists of Width gray values, in order from left to right. Each gray
value is a number from 0 through Maxval, with 0 being black and Maxval
being white. Each gray value is represented in pure binary by either
1 or 2 bytes. If the Maxval is less than 256, it is 1 byte.
Otherwise, it is 2 bytes. The most significant byte is first.
The format you are expecting is described further down below as the Plain PGM format. Its magic number is "P2".

OpenCV : Convert a CV_8UC3 image to a CV_32S1 image in C++

I need to convert a CV_8U image with 3 channels to an image which must be a single channel CV_32S. But when I'm trying to do so, the image I get is all black. I don't understand why my code is not working.
I'm dealing with a grayscale image, this is why I split the 3 channels image into a vector of single channel image, and then process only the first channel.
//markers->Image() returns a valid image, so this is not the problem
cv::Mat dst(markers->Image().size(), CV_32SC1);
dst = cv::Scalar::all(0);
std::vector<cv::Mat> vectmp;
cv::split(markers->Image(), vectmp);
vectmp.at(0).convertTo(dst, CV_32S);
//vectmp.at(0) is ok, but dst is black...?
Thank you in advance.
Have you tried to get values of result image? Like this:
for (int i=0; i<result.rows; i++)
{
for (int j=0; j<result.cols; j++)
{
cout << result.at<int>(i,j) << endl;
}
}
I have converted (also used convertTo) random gray-scale single-channel image to CV_32S (it is a signed 32bit integer value for each pixel) my output was like this:
80
111
132
And when I tried to show it I also get black image. From documentation:
If the image is 16-bit unsigned or 32-bit integer, the pixels are
divided by 256. That is, the value range [0,255*256] is mapped to
[0,255].
So if you divide these small numbers to 255 than you will get 0 (int type). That's why imshow displays black image.
If you want to display your 32-bit image and see a meaningful result, you need to multiply all of its elements by 256 prior to calling imshow. Otherwise, imshow will scale your values down to zero and you will get a black image (as Astor has pointed out).
Since the original values are 8 bit unsigned, they must be less than 255. Therefore multiplying them by 256 is safe and will not overflow a 32-bit integer.
EDIT I just realized your output type is a signed 32-bit integer, but the original type is unsigned 8-bit integer. In that case, you need to scale your values appropriately (have a look at scaleAdd).
Finally, you may want to make sure your image is in YCbCr format before you start throwing away image channels.
I had the same problem, solved it indirectly by trying to convert a 8UC1 to 32S instead of 8UC3.
RgbToGray accept to create a gray image using 8UC3 or 8UC1 element type.
8UC1 image is my marker image.
I've done this in Opencvsharp :
Mat buf3 = new Mat(iplImageMarker);
buf3.ConvertTo(buf3, MatType.CV_32SC1);
iplImageMarker= (IplImage)buf3;
iplImageMarker=iplImageMarker* 256;
I believe this is what you are looking for. Convert your image to this, 8 bit, single channel. CV_8UC1. You are starting with a 8 bit image and changing it to 32 bit single channel? Why? Keep it 8 bit.

cimg display rendering black

Using CImg; I'll keep this quick and simple.
CImg<float> i = *spectralImages->at(currentImage);
disp.display(i);
float* f = i.data();
disp is displaying a black image despite the fact that stepping through *(f), *(f+1), *(f+2), etc. is retrieving the correct numbers (255.0, 245.0, etc.)
I've been working on this all day. Is there a quirk with CImg that I'm missing?
EDIT:
Saving the file as a BMP seems to make the correct result, so there's just an issue with drawing it.
If your CImg image contains only a single value, or several equal values, the default display will display them as black images, because of the normalization applied to the pixel values for the display.
As CImg is able to manage any type of images (including float-valued), it always normalize the pixel values in [0,255] for the display (it does not change the pixel value in your object of course, it just normalizes them internally for its display).
So if your image has a single pixel values, the normalization will always result to '0', hence the black image as a result.
That means you probably didn't construct your CImgDisplay disp with the right pixel normalization argument (by default, it is enabled).
disp should be constructed like this :
CImgDisplay disp(100,100,"my display",0);
to disable the default normalization of pixel values.