I need to make a program that convert a RGB image to a GRAYSCALE image and save it in PGM format. I use DevIL library, but when I save the image, I obtain always a 3D image (3 matrix), in grayscale but, if I load it in MATLAB, I have 3 matrices instead of just one. How can I obtain just one matrix in my output file using DevIL?
int main()
{
ilInit();
ilEnable(IL_ORIGIN_SET);
ilOriginFunc(IL_ORIGIN_UPPER_LEFT);
ilEnable(IL_FILE_OVERWRITE);
ILuint ImageName; // The image name to return.
ilGenImages(1, &ImageName);
ilBindImage(ImageName);
if(!ilLoadImage("/home/andrea/Scrivania/tests/siftDemoV4/et000.jpg"))
{ printf("err");
exit;
}
else
printf("caricata\n");
ILuint width,height;
width = ilGetInteger(IL_IMAGE_WIDTH);
height = ilGetInteger(IL_IMAGE_HEIGHT);
double v[3]={0.2989360212937755001405548682669177651405334472656250000,0.5870430744511212495240215503145009279251098632812500000,0.1140209042551033058465748126764083281159400939941406250};
printf("%.55f %.55f %.55f",v[0],v[1],v[2]);
ILubyte *imgValue=ilGetData();
int i=0;
ILubyte imgNuova[width*height];
while( i < width*height)
{
imgNuova[i]=(char)round( ( (double)imgValue[3*i]*v[0])+ ( (double)imgValue[3*i+1]*v[1])+((double)imgValue[3*i+2]*v[2]));
i++;
}
ILuint ImageName2;
ilGenImages(2, &ImageName2);
ilBindImage(ImageName2);
ilTexImage(width, height, 1, 1, IL_LUMINANCE,
IL_UNSIGNED_BYTE, imgNuova);
iluFlipImage();
ilSave(IL_PNM,"/home/andrea/Scrivania/tests/siftDemoV4/et000new.pgm");
return 0;
}
Unfortunately, due to a bug in the PNM export, DevIL can and will only write PPM (Portable Pixmaps, 3 channel RGB) files regardless of the file extension. The only solution to this is to use a different file format, that supports single channel grayscale images, like PNG.
Matlab should be able to use that just as well. If you absolutely need or want files in the PGM format, you will have to use a converter like png2pnm.
Related
I am working with TIF images containing signed integer data. After successfully inputing one and processing it I need to output the image in the same format (input and output both *.tif files).
For the input, I know that OpenCV does not know if the data is signed or unsigned, so it assumes unsigned. Using this trick solves that problem (switching the type of cv::Mat by hand).
However, when I output the image and load it again, I do not get the expected result. The file contains multiple segments (groups of pixels), and the format is as follows (I must use this format):
all pixels not belonging to any segment have the value -9999
all the pixels belonging to a single segment have the same positive integer value
(e.g. all pixels of 1st segment have value 1, second 2 etc)
And here is the example code:
void ImageProcessor::saveSegments(const std::string &filename){
cv::Mat segmentation = cv::Mat(workingImage.size().height,
workingImage.size().width,
CV_32S, cv::Scalar(-9999));
for (int i=0, szi = segmentsInput.size(); i < szi; ++i){
for (int j=0, szj = segmentsInput[i].size(); j < szj; ++j){
segmentation.at<int>(segmentsInput[i][j].Y,
ssegmentsInput[i][j].X) = i+1;
}
}
cv::imwrite(filename, segmentation);
}
You can assume that all the variables (e.g. workingImage, segmentsInput) exist as global variables.
Using this code, when I input the image and examine the values, most of the values are set to 0 while the ones that are set take a full range of integer values (in my example I had 20 segments).
You can't save integer matrices directly with imwrite. As the documentation states: "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."
However, what you could do it to convert your CV_32S matrix to a CV_8UC4 and save it as a PNG with no compression. Of course, this is a bit unsafe since endianness comes into play and may change your values between different systems or compilers (especially since we're talking about signed integers here). If you use always the same system and compiler, you can use this:
cv::Mat segmentation = cv::Mat(workingImage.size().height,
workingImage.size().width,
CV_32S, cv::Scalar(-9999));
cv::Mat pngSegmentation(segmentation.rows, segmentation.cols, CV_8UC4, (cv::Vec4b*)segmentation.data);
std::vector<int> params;
params.push_back(CV_IMWRITE_PNG_COMPRESSION);
params.push_back(0);
cv::imwrite("segmentation.png", pngSegmentation, params);
I also save opencv mats as tifs but i don`t use the opencv tif solution. I include the libtiff lib on my own (i think libtiff is also used in opencv) and than you can use the following code to save as tiff
TIFF* tif = TIFFOpen("file.tif", "w");
if (tif != NULL) {
for (int i = 0; i < pages; i++)
{
TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, TIFF_UINT64_T(x)); // set the width of the image
TIFFSetField(tif, TIFFTAG_IMAGELENGTH, TIFF_UINT64_T(y)); // set the height of the image
TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 1); // set number of channels per pixel
TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, 32); // set the size of the channels 32 for CV_32F
TIFFSetField(tif, TIFFTAG_PAGENUMBER, i, pages);
TIFFSetField(tif, TIFFTAG_SAMPLEFORMAT, SAMPLEFORMAT_IEEEFP); // for CV32_F
for (uint32 row = 0; row < y; row++)
{
TIFFWriteScanline(tif, &imageDataStack[i].data[row*x*32/ 8], row, 0);
}
TIFFWriteDirectory(tif);
}
}
imageDataStack is a vector of cv::Mat objects. This code works for me to save tiff stacks.
Using opencv imwrite function I managed to convert jpg image in ppm P6 format.
Mat image = imread(picPath);
vector<int> compression_params;
compression_params.push_back(CV_IMWRITE_PXM_BINARY);
compression_params.push_back(1);
imwrite("bez.ppm", image, compression_params);
Problem is that I actually have to convert jpg image in ppm P3 ASCII format.
Does anyone know how to do it?
Thanks!
EDIT:
In the project I have the following piece of code where I check the maximum value of pixels:
int maxVal;
fscanf(in, "%d", &maxVal);
if (maxVal != 255)
{
printf("Input file error: Not a Netpbm color image with 256 levels\n");
exit(0);
}
When I set parameter 0 then I get: Not a Netpbm color image with 256 levels!
When I do the conversion from jpg to ppm p3 with irfanview program works.
The code involved is in the file modules/imgcodecs/src/grfmt_pxm.cpp in the OpenCV source tree.
It sets the internal flag isBinary like this according to the compression parameters:
for( size_t i = 0; i < params.size(); i += 2 )
if( params[i] == CV_IMWRITE_PXM_BINARY )
isBinary = params[i+1] != 0;
so, if you want ASCII (P3) you need to have
compression_params.push_back(0)
and have image type CV_8UC1, CV_8UC3 or CV_16UC1.
If I load an image into ImageMagick with the read function like so:
Magick::Image image;
image.read(filename);
how can I tell if the loaded image has an alpha channel? I want to direct my program to a different algorithm when I manipulate the pixels of a PNG with transparency vs when I load an opaque JPG.
Is there a simple yes/no test I can do?
The reason I am asking is because a code snippet like the following seems to assign random opacities if the loaded image does not have them, rather than assuming the pixel is completely opaque:
// transform the pixels to something GL can use
Magick::Pixels view(image);
GLubyte *pixels = (GLubyte*)malloc( sizeof(GLubyte)*width*height*4 );
for ( ssize_t row=0; row<height; row++ ) {
const Magick::PixelPacket *im_pixels = view.getConst(0,row,width,1);
for ( ssize_t col=0; col<width; col++ ) {
*(pixels+(row*width+col)*4+0) = (GLubyte)im_pixels[col].red;
*(pixels+(row*width+col)*4+1) = (GLubyte)im_pixels[col].green;
*(pixels+(row*width+col)*4+2) = (GLubyte)im_pixels[col].blue;
*(pixels+(row*width+col)*4+3) = 255-(GLubyte)im_pixels[col].opacity;
}
}
*pTex = pContext->LoadTexture( pixels, width, height );
free(pixels);
You can use the matte() property to determine if your image supports transparency.
Magick::Image image;
image.read(filename);
if (image.matte())
executeMethod(image);
A program I am using is reading some bitmaps, and expects 32FC1 images.
I am trying to create these images
cv::Mat M1(255, 255, CV_32FC1, cv::Scalar(0,0,0));
cv::imwrite( "my_bitmap.bmp", M1 );
but when I check the depth - it is always CV_8U
How can I create the files so that they will contain the correct info ?
Update: It makes no difference if I use a different file extension - e.g. tif or png
I am reading it - using code that is already implemented - with cvLoadImage.
I am trying to CREATE the files that the existing code - that checks for the image type - can use.
I cannot convert files in the existing code. The existing code does not try to read random image type and convert it to desired type, but checks that the files are of the type it needs.
I found out - thank you for the answers - that cv::imwrite only writes integer type images.
Is there another way - either using OpenCV or something else - to write the images so that I end up with CV_32F type ?
Update again:
The code to read image... if into a cv::Mat:
cv::Mat x = cv::imread(x_files, CV_LOAD_IMAGE_ANYDEPTH|CV_LOAD_IMAGE_ANYCOLOR);
The existing code:
IplImage *I = cvLoadImage(x_files.c_str(), CV_LOAD_IMAGE_ANYDEPTH|CV_LOAD_IMAGE_ANYCOLOR);
cv::imwrite() .bmp encoder assumes 8 bit channels.
If you only need to write .bmp files with OpenCV , you can convert your 32FC1 image to 8UC4, then use cv::imwrite() to write it and you will get a 32bits per pixel .bmp file.
I am guessing that your program that reads the file will interpret the 32 bit pixels as a 32FC1.
The .bmp format doesn't have an explicit channel structure, just a number of bits per pixel. Therefore you should be able to write 32 bit pixels as 4 channels of 8 bits in OpenCV and read them as single channel 32 bit pixels in another program - if you do this you need to be aware of endianness assumptions by the reader. Someting like the following should work:
cv::Mat m1(rows, cols, CV_32FC1);
... // fill m1
cv::Mat m2(rows, cols, CV_8UC4, m1.data); // provide different view of m1 data
// depending on endianess of reader, you may need to swap byte order of m2 pixels
cv::imwrite("my_bitmap.bmp", m2);
You will not be able to read properly the files you created in OpenCV because the .bmp decoder in OpenCV assumes the file is 1 or 3 channel of 8 bit data (i.e. it can't read 32 bit pixels).
EDIT
Probably a much better option would be to use the OpenEXR format, for which OpenCV has a codec. I assume you just need to save your files with a .exr extension.
Your problem is that bitmaps store data internally as integers not floats. If your problem is rounding error when saving you will need to either use a different file format or scale your data up before saving and then back down after saving. If you just want to convert the matrix you get after reading the file to a float you can use cv::convertto
I was struggling with the same problem. At the end i decided it would just be easier to write a custom function that can write and load an arbitrary CV Mat.
bool writeRawImage(const cv::Mat& image, const std::string& filename)
{
ofstream file;
file.open (filename, ios::out|ios::binary);
if (!file.is_open())
return false;
file.write(reinterpret_cast<const char *>(&image.rows), sizeof(int));
file.write(reinterpret_cast<const char *>(&image.cols), sizeof(int));
const int depth = image.depth();
const int type = image.type();
const int channels = image.channels();
file.write(reinterpret_cast<const char *>(&depth), sizeof(depth));
file.write(reinterpret_cast<const char *>(&type), sizeof(type));
file.write(reinterpret_cast<const char *>(&channels), sizeof(channels));
int sizeInBytes = image.step[0] * image.rows;
file.write(reinterpret_cast<const char *>(&sizeInBytes), sizeof(int));
file.write(reinterpret_cast<const char *>(image.data), sizeInBytes);
file.close();
return true;
}
bool readRawImage(cv::Mat& image, const std::string& filename)
{
int rows, cols, data, depth, type, channels;
ifstream file (filename, ios::in|ios::binary);
if (!file.is_open())
return false;
try {
file.read(reinterpret_cast<char *>(&rows), sizeof(rows));
file.read(reinterpret_cast<char *>(&cols), sizeof(cols));
file.read(reinterpret_cast<char *>(&depth), sizeof(depth));
file.read(reinterpret_cast<char *>(&type), sizeof(type));
file.read(reinterpret_cast<char *>(&channels), sizeof(channels));
file.read(reinterpret_cast<char *>(&data), sizeof(data));
image = cv::Mat(rows, cols, type);
file.read(reinterpret_cast<char *>(image.data), data);
} catch (...) {
file.close();
return false;
}
file.close();
return true;
}
Any C++ examples available to convert raw pixel data obtained from glReadPixels to JPEG format and back?
You can use ImageMagick library to convert raw data to the jpeg image data, and opposite. Using the same library, you can convert jpeg image data into raw (RGB) data.
Use an external library for that.
I'll recommend DevIL, your number one Swiss Army Knife for handling image files. You'll just need to
create an RGB image in memory via DevIL,
call glReadPixels to fill your DevIL image with pixels read from the GL framebuffer,
call ilSaveImage("foo.jpg") to save the file. You can also use bmp, png and a handful more - the format will get autodetected from the file name.
Simple.
I'm not sure if OpenGL has support for dealing with JPEG images. It's not what the library is really for.
Once you've got access to the pixel data, you should be able to use easily OpenCV to write the image to JPEG (or any other format), though. Here's some pseudo-code to get you going.
/*
* On Linux, compile with:
*
* g++ -Wall -ggdb -I. -I/usr/include/opencv -L /usr/lib -lm -lcv -lhighgui -lcvaux filename.cpp -o filename.out
*/
#include <cv.h>
#include <highgui.h>
/*
* Your image dimensions.
*/
int width;
int height;
CvSize size = cvSize(width, height);
/*
* Create 3-channel image, unsigned 8-bit per channel.
*/
IplImage *image = cvCreateImage(size, IPL_DEPTH_8U, 3);
for (int i = 0; i < width; ++i)
for (int j = 0; j < height; ++j)
{
unsigned int r;
unsigned int g;
unsigned int b;
/*
* Call glReadPixels, grab your RGB data.
* Keep in mind that OpenCV stores things in BGR order.
*/
CvScalar bgr = cvScalar(b, g, r);
cvSet2D(image, i, j, bgr);
}
cvSaveImage("filename.jpg", image);
cvReleaseImage(&image);
Other libraries for dealing with JPEG also exist, if you look around.