OpenCV: how to read .pfm files? - c++

Is there a way to read .pfm files in OpenCV?
Thank you very much for any suggestions!

PFM is an uncommon image format and I don't know why the Middlebury dataset chose to use that, probably because it uses floating point values.
Anyway I was able to read the images with OpenCV:
import numpy as np
import cv2
groundtruth = cv2.imread('disp0.pfm', cv2.IMREAD_UNCHANGED)
Note the IMREAD_UNCHANGED flag. Somehow it is able to read all the correct values even if OpenCV does not support it.
But wait a minute: inf values are commonly used to set INVALID pixel disparity, so to properly display the image you should do:
# Remove infinite value to display
groundtruth[groundtruth==np.inf] = 0
# Normalize and convert to uint8
groundtruth = cv2.normalize(groundtruth, None, 0, 255, cv2.NORM_MINMAX).astype(np.uint8)
# Show
cv2.imshow("groundtruth", groundtruth)
cv2.waitKey(0)
cv2.destroyAllWindows()

Based on the description of the ".pfm" file formate (see http://netpbm.sourceforge.net/doc/pfm.html), I wrote the following read/write functions, which only depend standard C/C++ library. It is proved to work well on reading/writing the pfm file, like, the ground truth disparity ".pfm" files from MiddleBury Computer Vision (see http://vision.middlebury.edu/stereo/submit3/).
#ifndef _PGM_H_
#define _PGM_H_
#include <fstream>
#include <iostream>
#include <algorithm>
#include <string>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <bitset> /*std::bitset<32>*/
#include <cstdio>
enum PFM_endianness { BIG, LITTLE, ERROR};
class PFM {
public:
PFM();
inline bool is_little_big_endianness_swap(){
if (this->endianess == 0.f) {
std::cerr << "this-> endianness is not assigned yet!\n";
exit(0);
}
else {
uint32_t endianness = 0xdeadbeef;
//std::cout << "\n" << std::bitset<32>(endianness) << std::endl;
unsigned char * temp = (unsigned char *)&endianness;
//std::cout << std::bitset<8>(*temp) << std::endl;
PFM_endianness endianType_ = ((*temp) ^ 0xef == 0 ?
LITTLE : (*temp) ^ (0xde) == 0 ? BIG : ERROR);
// ".pfm" format file specifies that:
// positive scale means big endianess;
// negative scale means little endianess.
return ((BIG == endianType_) && (this->endianess < 0.f))
|| ((LITTLE == endianType_) && (this->endianess > 0.f));
}
}
template<typename T>
T * read_pfm(const std::string & filename) {
FILE * pFile;
pFile = fopen(filename.c_str(), "rb");
char c[100];
if (pFile != NULL) {
fscanf(pFile, "%s", c);
// strcmp() returns 0 if they are equal.
if (!strcmp(c, "Pf")) {
fscanf(pFile, "%s", c);
// atoi: ASCII to integer.
// itoa: integer to ASCII.
this->width = atoi(c);
fscanf(pFile, "%s", c);
this->height = atoi(c);
int length_ = this->width * this->height;
fscanf(pFile, "%s", c);
this->endianess = atof(c);
fseek(pFile, 0, SEEK_END);
long lSize = ftell(pFile);
long pos = lSize - this->width*this->height * sizeof(T);
fseek(pFile, pos, SEEK_SET);
T* img = new T[length_];
//cout << "sizeof(T) = " << sizeof(T);
fread(img, sizeof(T), length_, pFile);
fclose(pFile);
/* The raster is a sequence of pixels, packed one after another,
* with no delimiters of any kind. They are grouped by row,
* with the pixels in each row ordered left to right and
* the rows ordered bottom to top.
*/
T* tbimg = (T *)malloc(length_ * sizeof(T));// top-to-bottom.
//PFM SPEC image stored bottom -> top reversing image
for (int i = 0; i < this->height; i++) {
memcpy(&tbimg[(this->height - i - 1)*(this->width)],
&img[(i*(this->width))],
(this->width) * sizeof(T));
}
if (this->is_little_big_endianness_swap()){
std::cout << "little-big endianness transformation is needed.\n";
// little-big endianness transformation is needed.
union {
T f;
unsigned char u8[sizeof(T)];
} source, dest;
for (int i = 0; i < length_; ++i) {
source.f = tbimg[i];
for (unsigned int k = 0, s_T = sizeof(T); k < s_T; k++)
dest.u8[k] = source.u8[s_T - k - 1];
tbimg[i] = dest.f;
//cout << dest.f << ", ";
}
}
delete[] img;
return tbimg;
}
else {
std::cout << "Invalid magic number!"
<< " No Pf (meaning grayscale pfm) is missing!!\n";
fclose(pFile);
exit(0);
}
}
else {
std::cout << "Cannot open file " << filename
<< ", or it does not exist!\n";
fclose(pFile);
exit(0);
}
}
template<typename T>
void write_pfm(const std::string & filename, const T* imgbuffer,
const float & endianess_) {
std::ofstream ofs(filename.c_str(), std::ifstream::binary);
// ** 1) Identifier Line: The identifier line contains the characters
// "PF" or "Pf". PF means it's a color PFM.
// Pf means it's a grayscale PFM.
// ** 2) Dimensions Line:
// The dimensions line contains two positive decimal integers,
// separated by a blank. The first is the width of the image;
// the second is the height. Both are in pixels.
// ** 3) Scale Factor / Endianness:
// The Scale Factor / Endianness line is a queer line that jams
// endianness information into an otherwise sane description
// of a scale. The line consists of a nonzero decimal number,
// not necessarily an integer. If the number is negative, that
// means the PFM raster is little endian. Otherwise, it is big
// endian. The absolute value of the number is the scale
// factor for the image.
// The scale factor tells the units of the samples in the raster.
// You use somehow it along with some separately understood unit
// information to turn a sample value into something meaningful,
// such as watts per square meter.
ofs << "Pf\n"
<< this->width << " " << this->height << "\n"
<< endianess_ << "\n";
/* PFM raster:
* The raster is a sequence of pixels, packed one after another,
* with no delimiters of any kind. They are grouped by row,
* with the pixels in each row ordered left to right and
* the rows ordered bottom to top.
* Each pixel consists of 1 or 3 samples, packed one after another,
* with no delimiters of any kind. 1 sample for a grayscale PFM
* and 3 for a color PFM (see the Identifier Line of the PFM header).
* Each sample consists of 4 consecutive bytes. The bytes represent
* a 32 bit string, in either big endian or little endian format,
* as determined by the Scale Factor / Endianness line of the PFM
* header. That string is an IEEE 32 bit floating point number code.
* Since that's the same format that most CPUs and compiler use,
* you can usually just make a program use the bytes directly
* as a floating point number, after taking care of the
* endianness variation.
*/
int length_ = this->width*this->height;
this->endianess = endianess_;
T* tbimg = (T *)malloc(length_ * sizeof(T));
// PFM SPEC image stored bottom -> top reversing image
for (int i = 0; i < this->height; i++) {
memcpy(&tbimg[(this->height - i - 1)*this->width],
&imgbuffer[(i*this->width)],
this->width * sizeof(T));
}
if (this->is_little_big_endianness_swap()) {
std::cout << "little-big endianness transformation is needed.\n";
// little-big endianness transformation is needed.
union {
T f;
unsigned char u8[sizeof(T)];
} source, dest;
for (int i = 0; i < length_; ++i) {
source.f = tbimg[i];
for (size_t k = 0, s_T = sizeof(T); k < s_T; k++)
dest.u8[k] = source.u8[s_T - k - 1];
tbimg[i] = dest.f;
//cout << dest.f << ", ";
}
}
ofs.write((char *)tbimg, this->width*this->height * sizeof(T));
ofs.close();
free(tbimg);
}
inline float getEndianess(){return endianess;}
inline int getHeight(void){return height;}
inline int getWidth(void){return width;}
inline void setHeight(const int & h){height = h;}
inline void setWidth(const int & w){width = w;}
private:
int height;
int width;
float endianess;
};
#endif /* PGM_H_ */
Forgive me to leave lots of useless comments in the code.
A simple example shows the write/read:
int main(){
PFM pfm_rw;
string temp = "img/Motorcycle/disp0GT.pfm";
float * p_disp_gt = pfm_rw.read_pfm<float>(temp);
//int imgH = pfm_rw.getHeight();
//int imgW = pfm_rw.getWidth();
//float scale = pfm_rw.getEndianess();
string temp2 = "result/Motorcycle/disp0GT_n1.pfm";
pfm_rw.write_pfm<float>(temp2, p_disp_gt, -1.0f);
return 1;
}

As far as I know, OpenCV doesn't support to read PFM files directly.
You can refer to the code snippet here for a simple PFM reader, which will enable you to read PFM files into COLOR *data with COLOR defined as follows:
typedef struct {
float r;
float g;
float b;
} COLOR;

Related

What's the correct way to assign one GPU memory buffer value from another GPU memory buffer with some arithmetic on each source buffer's element?

I'm a newbie for GPU programming using Cuda toolkit, and I have to write some code offering the functionality as I mentioned in the title.
I'd like to paste the code to show what exactly I want to do.
void CTrtModelWrapper::forward(void **bindings,
unsigned height,
unsigned width,
short channel,
ColorSpaceFmt colorFmt,
PixelDataType pixelType) {
uint16_t *devInRawBuffer_ptr = (uint16_t *) bindings[0];
uint16_t *devOutRawBuffer_ptr = (uint16_t *) bindings[1];
const unsigned short bit = 16;
float *devInputBuffer_ptr = nullptr;
float *devOutputBuffer_ptr = nullptr;
unsigned volume = height * width * channel;
common::cudaCheck(cudaMalloc((void **) &devInputBuffer_ptr, volume * getElementSize(nvinfer1::DataType::kFLOAT)));
common::cudaCheck(cudaMalloc((void **) &devOutputBuffer_ptr, volume * getElementSize(nvinfer1::DataType::kFLOAT)));
unsigned short npos = 0;
switch (pixelType) {
case PixelDataType::PDT_INT8: // high 8bit
npos = bit - 8;
break;
case PixelDataType::PDT_INT10: // high 10bit
npos = bit - 10;
break;
default:
break;
}
switch (colorFmt) {
case CFMT_RGB: {
for (unsigned i = 0; i < volume; ++i) {
devInputBuffer_ptr[i] = float((devInRawBuffer_ptr[i]) >> npos); // SEGMENTATION Fault at this line
}
}
break;
default:
break;
}
void *rtBindings[2] = {devInputBuffer_ptr, devOutputBuffer_ptr};
// forward
this->_forward(rtBindings);
// convert output
unsigned short ef_bit = bit - npos;
switch (colorFmt) {
case CFMT_RGB: {
for (unsigned i = 0; i < volume; ++i) {
devOutRawBuffer_ptr[i] = clip< uint16_t >((uint16_t) devOutputBuffer_ptr[i],
0,
(uint16_t) pow(2, ef_bit)) << npos;
}
}
break;
default:
break;
}
}
bindings is a pointer to an array, the 1st element in the array is a device pointer that points to a buffer allocated using cudaMalloc on the gpu, each element in the buffer is a 16bit integer.the 2nd one the same, used to store the output data.
height,width,channel,colorFmt(RGB here),pixelType(PDT_INT8, aka 8bit) respective to the image height, width,channel number, colorspace, bits to store one pixel value.
the _forward function requires a pointer to an array, similar to bindings except that each element in the buffer should be a 32bit float number.
so I make some transformation using a loop
for (unsigned i = 0; i < volume; ++i) {
devInputBuffer_ptr[i] = float((devInRawBuffer_ptr[i]) >> npos); // SEGMENTATION Fault at this line
}
the >> operation is because the actual 8bit data is stored in the high 8 bit.
SEGMENTATION FAULT occurred at this line of code devInputBuffer_ptr[i] = float((devInRawBuffer_ptr[i]) >> npos); and i equals 0.
I try to separate this code into several line:
uint16_t value = devInRawBuffer_ptr[i];
float transferd = float(value >> npos);
devInputBuffer_ptr[i] = transferd;
and SEGMENTATION FAULT occurred at this line uint16_t value = devInRawBuffer_ptr[i];
I wonder that is this a valid way to assign value to an allocated gpu memory buffer?
PS: the buffer given in bindings are totally fine. they are from host memory using cudaMemcpy before the call to forward function, but I still paste the code below
nvinfer1::DataType type = nvinfer1::DataType::kHALF;
HostBuffer hostInputBuffer(volume, type);
DeviceBuffer deviceInputBuffer(volume, type);
HostBuffer hostOutputBuffer(volume, type);
DeviceBuffer deviceOutputBuffer(volume, type);
// HxWxC --> WxHxC
auto *hostInputDataBuffer = static_cast<unsigned short *>(hostInputBuffer.data());
for (unsigned w = 0; w < W; ++w) {
for (unsigned h = 0; h < H; ++h) {
for (unsigned c = 0; c < C; ++c) {
hostInputDataBuffer[w * H * C + h * C + c] = (unsigned short )(*(ppm.buffer.get() + h * W * C + w * C + c));
}
}
}
auto ret = cudaMemcpy(deviceInputBuffer.data(), hostInputBuffer.data(), volume * getElementSize(type),
cudaMemcpyHostToDevice);
if (ret != 0) {
std::cout << "CUDA failure: " << ret << std::endl;
return EXIT_FAILURE;
}
void *bindings[2] = {deviceInputBuffer.data(), deviceOutputBuffer.data()};
model->forward(bindings, H, W, C, sbsisr::ColorSpaceFmt::CFMT_RGB, sbsisr::PixelDataType::PDT_INT8);
In CUDA, it's generally not advisable to dereference a device pointer in host code. For example, you are creating a "device pointer" when you use cudaMalloc:
common::cudaCheck(cudaMalloc((void **) &devInputBuffer_ptr, volume * getElementSize(nvinfer1::DataType::kFLOAT)));
From the code you have posted, it's not possible to deduce that for devInRawBuffer_ptr but I'll assume it also is a device pointer.
In that case, to perform this operation:
for (unsigned i = 0; i < volume; ++i) {
devInputBuffer_ptr[i] = float((devInRawBuffer_ptr[i]) >> npos);
}
You would launch a CUDA kernel, something like this:
// put this function definition at file scope
__global__ void shift_kernel(float *dst, uint16_t *src, size_t sz, unsigned short npos){
for (size_t idx = blockIdx.x*blockDim.x+threadIdx.x, idx < sz; idx += gridDim.x*blockDim.x) dst[idx] = (float)((src[idx]) >> npos);
}
// call it like this in your code:
kernel<<<160, 1024>>>(devInputBuffer_ptr, devInRawBuffer_ptr, volume, npos);
(coded in browser, not tested)
If you'd like to learn more about what's going on here, you may wish to study CUDA. For example, you can get most of the basic concepts here and by studying the CUDA sample code vectorAdd. The grid-stride loop is discussed here.

C++: Write BMP image format error on WINDOWS

I have the most strange problem here... I'm using the same code(copy-paste) from Linux in Windows to READ and WRITE and BMP image. And from some reason in Linux every thing works perfectly fine, but when I'm coming to Windows 10 from some I can't open that images and I've receive an error message how said something like this:
"It looks like we don't support this file format."
Do you have any idea what should I do? I will put the code below.
EDIT:
I've solved the padding problem and now it's write the images but they are completely white, any idea why? I've update the code also.
struct BMP {
int width;
int height;
unsigned char header[54];
unsigned char *pixels;
int size;
int row_padded;
};
void writeBMP(string filename, BMP image) {
string fileName = "Output Files\\" + filename;
FILE *out = fopen(fileName.c_str(), "wb");
fwrite(image.header, sizeof(unsigned char), 54, out);
unsigned char tmp;
for (int i = 0; i < image.height; i++) {
for (int j = 0; j < image.width * 3; j += 3) {
// Convert (B, G, R) to (R, G, B)
tmp = image.pixels[j];
image.pixels[j] = image.pixels[j + 2];
image.pixels[j + 2] = tmp;
}
fwrite(image.pixels, sizeof(unsigned char), image.row_padded, out);
}
fclose(out);
}
BMP readBMP(string filename) {
BMP image;
string fileName = "Input Files\\" + filename;
FILE *f = fopen(fileName.c_str(), "rb");
if (f == NULL)
throw "Argument Exception";
fread(image.header, sizeof(unsigned char), 54, f); // read the 54-byte header
// extract image height and width from header
image.width = *(int *) &image.header[18];
image.height = *(int *) &image.header[22];
image.row_padded = (image.width * 3 + 3) & (~3);
image.pixels = new unsigned char[image.row_padded];
unsigned char tmp;
for (int i = 0; i < image.height; i++) {
fread(image.pixels, sizeof(unsigned char), image.row_padded, f);
for (int j = 0; j < image.width * 3; j += 3) {
// Convert (B, G, R) to (R, G, B)
tmp = image.pixels[j];
image.pixels[j] = image.pixels[j + 2];
image.pixels[j + 2] = tmp;
}
}
fclose(f);
return image;
}
In my point of view this code should be cross-platform... But it's not... why?
Thanks for help
Check the header
The header must start with the following two signature bytes: 0x42 0x4D. If it's something different a third party application will think that this file doesn't contain a bmp picture despite the .bmp file extension.
The size and the way pixels are stored is also a little bit more complex than what you expect: you assume that the number of bits per pixels is 24 and no no compression is used. This is not guaranteed. If it's not the case, you might read more data than available, and corrupt the file when writing it back.
Furthermore, the size of the header depends also on the BMP version you are using, which you can detect using the 4 byte integer at offset 14.
Improve your code
When you load a file, check the signature, the bmp version, the number of bits per pixel and the compression. For debugging purpose, consider dumping the header to check it manually:
for (int i=0; i<54; i++)
cout << hex << image.header[i] << " ";`
cout <<endl;
Furthermore, when you fread() check that the number of bytes read correspond to the size you wanted to read, so to be sure that you're not working with uninitialized buffer data.
Edit:
Having checked the dump, it appears that the format is as expected. But verifying the padded size in the header with the padded size that you have calculated it appears that the error is here:
image.row_padded = (image.width * 3 + 3) & (~3); // ok size of a single row rounded up to multiple of 4
image.pixels = new unsigned char[image.row_padded]; // oops ! A little short ?
In fact you read row by row, but you only keep the last one in memory ! This is different of your first version, where you did read the full pixels of the picture.
Similarly, you write the last row repeated height time.
Reconsider your padding, working with the total padded size.
image.row_padded = (image.width * 3 + 3) & (~3); // ok size of a single row rounded up to multiple of 4
image.size_padded = image.row_padded * image.height; // padded full size
image.pixels = new unsigned char[image.size_padded]; // yeah !
if (fread(image.pixels, sizeof(unsigned char), image.size_padded, f) != image.size_padded) {
cout << "Error: all bytes couldn't be read"<<endl;
}
else {
... // process the pixels as expected
}
...

Reading double values and int values from binary file

I have a binary file which has the following format :
# vtk DataFile Version 4.0
vtk output
BINARY
DATASET POLYDATA
POINTS 10000 double
?�T�����?����h�?�T�����?���� <-- 10000 double values (in binary format) follow separated by space and new line after every 9 values.
I want to read this file byte by byte so that I can store these double values in my array. I have the following code which loads this file into a char *buffer array. Now I want to know how to proceed further?
#include<iostream>
#include<fstream>
#include<sstream>
#include<stdlib.h>
#include<string>
using namespace std;
int main () {
ifstream is ("Data_binary.vtk", ifstream::binary);
if (is) {
// get length of file:
is.seekg (0, is.end);
unsigned long length = is.tellg();
is.seekg (0, is.beg);
char * buffer = new char [length+1];
buffer[length] = '\0';
cout << "Reading " << length << " characters... ";
// read data as a block:
is.seekg(0, is.beg);
is.read (buffer,length);
if (is)
cout << "all characters read successfully." << endl;
else
cout << "error: only " << is.gcount() << " could be read";
is.close();
}
return 0;
}
In ASCII format, an example file would look like the following :
# vtk DataFile Version 4.0
vtk output
ASCII
DATASET POLYDATA
POINTS 18 double
.1 .2 .3 1.4 11.55 1 0 8e-03 5.6
1.02 2.2 3.3 .1 .5 0.001 4e-07 4.2 1.55
For binary file, the double values are present in binary. I want to get double values from binary format.
Use this function.
/*
* read a double from a stream in ieee754 format regardless of host
* encoding.
* fp - the stream
* bigendian - set to if big bytes first, clear for little bytes
* first
*
*/
double freadieee754(FILE *fp, int bigendian)
{
unsigned char buff[8];
int i;
double fnorm = 0.0;
unsigned char temp;
int sign;
int exponent;
double bitval;
int maski, mask;
int expbits = 11;
int significandbits = 52;
int shift;
double answer;
/* read the data */
for (i = 0; i < 8; i++)
buff[i] = fgetc(fp);
/* just reverse if not big-endian*/
if (!bigendian)
{
for (i = 0; i < 4; i++)
{
temp = buff[i];
buff[i] = buff[8 - i - 1];
buff[8 - i - 1] = temp;
}
}
sign = buff[0] & 0x80 ? -1 : 1;
/* exponet in raw format*/
exponent = ((buff[0] & 0x7F) << 4) | ((buff[1] & 0xF0) >> 4);
/* read inthe mantissa. Top bit is 0.5, the successive bits half*/
bitval = 0.5;
maski = 1;
mask = 0x08;
for (i = 0; i < significandbits; i++)
{
if (buff[maski] & mask)
fnorm += bitval;
bitval /= 2.0;
mask >>= 1;
if (mask == 0)
{
mask = 0x80;
maski++;
}
}
/* handle zero specially */
if (exponent == 0 && fnorm == 0)
return 0.0;
shift = exponent - ((1 << (expbits - 1)) - 1); /* exponent = shift + bias */
/* nans have exp 1024 and non-zero mantissa */
if (shift == 1024 && fnorm != 0)
return sqrt(-1.0);
/*infinity*/
if (shift == 1024 && fnorm == 0)
{
#ifdef INFINITY
return sign == 1 ? INFINITY : -INFINITY;
#endif
return (sign * 1.0) / 0.0;
}
if (shift > -1023)
{
answer = ldexp(fnorm + 1.0, shift);
return answer * sign;
}
else
{
/* denormalised numbers */
if (fnorm == 0.0)
return 0.0;
shift = -1022;
while (fnorm < 1.0)
{
fnorm *= 2;
shift--;
}
answer = ldexp(fnorm, shift);
return answer * sign;
}
}
it's a lot, but it's just a snippet to cut and paste, and you never need to worry about binary floating point formats again. It simply reads an IEEE 754 double, regardless of host floating point format.
There's a twin which writes
Instead of reading into a char * buffer, read into a double * buffer. Casting to/from char * is allowed just for this purpose.
vector<double> buffer;
buffer.resize(n);
is.read(reinterpret_cast<char *>(&buffer[0]), n * sizeof(buffer[0]));
You'll need to read the non-binary data first so that the file pointer is located at the start of the binary data. This is defined as coming immediately after the newline character of the last field in the header.
The spec doesn't appear to mandate little-endian or big-endian format, it expects you to know based on the source of the file. If you're lucky the format will match the machine you're using to read the file and no conversion will be necessary. Otherwise you'll need to do a byte swap:
void ByteSwap(double * p)
{
char * pc = reinterpret_cast<char *>(p);
std::swap(pc[0], pc[7]);
std::swap(pc[1], pc[6]);
std::swap(pc[2], pc[5]);
std::swap(pc[3], pc[4]);
}

Working with BMP - loading and saving

With friends we're trying to write app to work with BMP files and we're going to make it as simple as it could be for us, because we're just starting to learn C and C++. Copying was going good with new real size of lines but now I wanted to add grayscale effect and got another problem: the right side of the picture is moved to the left - check out pictures. What's causing this problem?
#include <iostream>
#include <fstream>
#include <stdio.h>
#include <unistd.h>
using namespace std;
void ReadBMP()
{
FILE* f = fopen("test2.bmp", "rb");
FILE* w = fopen("zapis.bmp", "wb");
if(f == NULL)
throw "Argument Exception";
unsigned char info[54];
fread(info, sizeof(unsigned char), 54, f);
fwrite(info, sizeof(unsigned char), 54, w);
int width = *(int*)&info[18];
int height = *(int*)&info[22];
cout << endl;
cout << "Width: " << width << endl;
cout << "Height: " << height << endl;
int realwidth = 3*width+(4 - ((3*width)%4))%4;
int volume = height * realwidth;
unsigned char* data = new unsigned char[volume];
fwrite(info, sizeof(unsigned char), 54, w);
fread(data, sizeof(unsigned char), volume, f);
unsigned char color = 0;
for(int i = 0; i < volume; i+=3)
{
color = 0;
color+=data[i]*0.114;
color+=data[i+1]*0.587;
color+=data[i+2]*0.299;
data[i] = color;
data[i+1] = color;
data[i+2] = color;
}
fwrite(data, sizeof(unsigned char), volume, w);
fclose(f);
fclose(w);
delete(data);
}
int main()
{
ReadBMP();
return 0;
}
Input image
Output image
Your formula for the size of the image data is wrong. First you need to find the pitch, by multiplying the width by the bytes per pixel (3 for a 24-bit image), and then rounding up to the nearest multiple of 4. Then multiply the pitch by the height;
int byte_width = width * 3;
int pitch = byte_width + (4 - byte_width % 4) % 4;
int volume = pitch * height;
unsigned char info[54];
fread(info, sizeof(unsigned char), 54, f);
// fwrite(info, sizeof(unsigned char), 54, w); --- comment this line !!!!!!!
int width = *(int*)&info[18];
int height = *(int*)&info[22];
You're writing header to file twice unnecessarily.
As I see in my own code (that was written about 20 years ago) each line of the image is complemented by 0 or more empty bytes to align the start byte. It seems, your calculate wrong alignment.
Just copy & paste here:
unsigned short paddingSize;
unsigned short bitsPerLine = width * bitsPerPixel;
if(1 == bitsPerPixel || 4 == bitsPerPixel)
{
if(bitsPerLine % 8)
bitsPerLine += 8;
paddingSize = (bitsPerLine/8) % 2;
}
else if(8 == bitsPerPixel)
paddingSize = 0x0003 & ~((bitsPerLine/8) % 4 - 1);
else
paddingSize = (bitsPerLine/8) % 2;
Real size of each line is calculatedSize + paddingSize where calculatedSize is exact size of line in bytes i.e. ceil(bitsPerLine/8) or (bitsPerLine + 7)/8 ic C/C++.
What I can say about the code is it's debugged and it works. But I don't remember why all these checks here.

C++: Convert text file of integers into a bitmap image file in BMP format

I have a text file being saved by a matrix library containing a 2D matrix as such:
1 0 0
6 0 4
0 1 1
Where each number is represented with a colored pixel. I am looking for some insight as to how I'd go about solving this problem. If any more information is required, do not hesitate to ask.
EDIT: Another approach I've tried is: fwrite(&intmatrix, size,1, bmp_ptr); where I pass in the matrix pointer, which does not seem to output a readable BMP file. The value of size is the rows*cols of course, and the type of matrix is arma::Mat<int> which is a matrix from the Armadillo Linear Algebra Library.
EDIT II: Reading this indicated that my size should probably be rows*cols*4 given the size of the rows if I am not mistaken, any guidance on this point as well would be great.
Here's an app which generates a text file of random integers, reads them back, and writes them to disk as a (roughly square) 32-bit-per-pixel .BMP image.
Note, I made a number of assumptions on things like the format of the original text file, the range of numbers, etc., but they are documented in the code. With this working example you should be able to tweak them easily, if necessary.
// IntToBMP.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <cstdint>
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <random>
#include <ctime>
#include <memory>
#pragma pack( push, 1 )
struct BMP
{
BMP();
struct
{
uint16_t ID;
uint32_t fileSizeInBytes;
uint16_t reserved1;
uint16_t reserved2;
uint32_t pixelArrayOffsetInBytes;
} FileHeader;
enum class CompressionMethod : uint32_t { BI_RGB = 0x00,
BI_RLE8 = 0x01,
BI_RLE4 = 0x02,
BI_BITFIELDS = 0x03,
BI_JPEG = 0x04,
BI_PNG = 0x05,
BI_ALPHABITFIELDS = 0x06 };
struct
{
uint32_t headerSizeInBytes;
uint32_t bitmapWidthInPixels;
uint32_t bitmapHeightInPixels;
uint16_t colorPlaneCount;
uint16_t bitsPerPixel;
CompressionMethod compressionMethod;
uint32_t bitmapSizeInBytes;
int32_t horizontalResolutionInPixelsPerMeter;
int32_t verticalResolutionInPixelsPerMeter;
uint32_t paletteColorCount;
uint32_t importantColorCount;
} DIBHeader;
};
#pragma pack( pop )
BMP::BMP()
{
//Initialized fields
FileHeader.ID = 0x4d42; // == 'BM' (little-endian)
FileHeader.reserved1 = 0;
FileHeader.reserved2 = 0;
FileHeader.pixelArrayOffsetInBytes = sizeof( FileHeader ) + sizeof( DIBHeader );
DIBHeader.headerSizeInBytes = 40;
DIBHeader.colorPlaneCount = 1;
DIBHeader.bitsPerPixel = 32;
DIBHeader.compressionMethod = CompressionMethod::BI_RGB;
DIBHeader.horizontalResolutionInPixelsPerMeter = 2835; // == 72 ppi
DIBHeader.verticalResolutionInPixelsPerMeter = 2835; // == 72 ppi
DIBHeader.paletteColorCount = 0;
DIBHeader.importantColorCount = 0;
}
void Exit( void )
{
std::cout << "Press a key to exit...";
std::getchar();
exit( 0 );
}
void MakeIntegerFile( const std::string& integerFilename )
{
const uint32_t intCount = 1 << 20; //Generate 1M (2^20) integers
std::unique_ptr< int32_t[] > buffer( new int32_t[ intCount ] );
std::mt19937 rng;
uint32_t rngSeed = static_cast< uint32_t >( time( NULL ) );
rng.seed( rngSeed );
std::uniform_int_distribution< int32_t > dist( INT32_MIN, INT32_MAX );
for( size_t i = 0; i < intCount; ++i )
{
buffer[ i ] = dist( rng );
}
std::ofstream writeFile( integerFilename, std::ofstream::binary );
if( !writeFile )
{
std::cout << "Error writing " << integerFilename << ".\n";
Exit();
}
writeFile << buffer[ 0 ];
for( size_t i = 1; i < intCount; ++i )
{
writeFile << " " << buffer[ i ];
}
}
int _tmain(int argc, _TCHAR* argv[]) //Replace with int main( int argc, char* argv[] ) if you're not under Visual Studio
{
//Assumption: 32-bit signed integers
//Assumption: Distribution of values range from INT32_MIN through INT32_MAX, inclusive
//Assumption: number of integers contained in file are unknown
//Assumption: source file of integers is a series of space-delimitied strings representing integers
//Assumption: source file's contents are valid
//Assumption: non-rectangular numbers of integers yield non-rectangular bitmaps (final scanline may be short)
// This may cause some .bmp parsers to fail; others may pad with 0's. For simplicity, this implementation
// attempts to render square bitmaps.
const std::string integerFilename = "integers.txt";
const std::string bitmapFilename = "bitmap.bmp";
std::cout << "Creating file of random integers...\n";
MakeIntegerFile( integerFilename );
std::vector< int32_t >integers; //If quantity of integers being read is known, reserve or resize vector or use array
//Read integers from file
std::cout << "Reading integers from file...\n";
{ //Nested scope will release ifstream resource when no longer needed
std::ifstream readFile( integerFilename );
if( !readFile )
{
std::cout << "Error reading " << integerFilename << ".\n";
Exit();
}
std::string number;
while( readFile.good() )
{
std::getline( readFile, number, ' ' );
integers.push_back( std::stoi( number ) );
}
if( integers.size() == 0 )
{
std::cout << "No integers read from " << integerFilename << ".\n";
Exit();
}
}
//Construct .bmp
std::cout << "Constructing .BMP...\n";
BMP bmp;
size_t intCount = integers.size();
bmp.DIBHeader.bitmapSizeInBytes = intCount * sizeof( integers[ 0 ] );
bmp.FileHeader.fileSizeInBytes = bmp.FileHeader.pixelArrayOffsetInBytes + bmp.DIBHeader.bitmapSizeInBytes;
bmp.DIBHeader.bitmapWidthInPixels = static_cast< uint32_t >( ceil( sqrt( intCount ) ) );
bmp.DIBHeader.bitmapHeightInPixels = static_cast< uint32_t >( ceil( intCount / static_cast< float >( bmp.DIBHeader.bitmapWidthInPixels ) ) );
//Write integers to .bmp file
std::cout << "Writing .BMP...\n";
{
std::ofstream writeFile( bitmapFilename, std::ofstream::binary );
if( !writeFile )
{
std::cout << "Error writing " << bitmapFilename << ".\n";
Exit();
}
writeFile.write( reinterpret_cast< char * >( &bmp ), sizeof( bmp ) );
writeFile.write( reinterpret_cast< char * >( &integers[ 0 ] ), bmp.DIBHeader.bitmapSizeInBytes );
}
//Exit
Exit();
}
Hope this helps.
If you choose the right image format this is very easy. PGM has an ASCII variant that looks almost exactly like your matrix, but with a header.
P2
3 3
6
1 0 0
6 0 4
0 1 1
Where P2 is the magic for ASCII PGM, the size is 3x3 and 6 is the maxval. I chose 6 because that was the maximum value you presented, which makes 6 white (while 0 is black). In a typical PGM that's 255, which is consistent with an 8-bit grayscale image.
PPM is almost as simple, it just has 3 color components per pixel instead of 1.
You can operate on these images with anything that takes PPM (netpbm, ImageMagick, GIMP, etc). You can resave them as binary PPMs which are basically the same size as an equivalent BMP.
To output a readable BMP file, you need to put a header first:
#include <WinGDI.h>
DWORD dwSizeInBytes = rows*cols*4; // when your matrix contains RGBX data)
// fill in the headers
BITMAPFILEHEADER bmfh;
bmfh.bfType = 0x4D42; // 'BM'
bmfh.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + dwSizeInBytes;
bmfh.bfReserved1 = 0;
bmfh.bfReserved2 = 0;
bmfh.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
BITMAPINFOHEADER bmih;
bmih.biSize = sizeof(BITMAPINFOHEADER);
bmih.biWidth = cols;
bmih.biHeight = rows;
bmih.biPlanes = 1;
bmih.biBitCount = 32;
bmih.biCompression = BI_RGB;
bmih.biSizeImage = 0;
bmih.biXPelsPerMeter = 0;
bmih.biYPelsPerMeter = 0;
bmih.biClrUsed = 0;
bmih.biClrImportant = 0;
Now before you write your color information, just write the bitmap header
fwrite(&bmfh, sizeof(bmfh),1, bmp_ptr);
fwrite(&bmih, sizeof(bmih),1, bmp_ptr);
And finally the color information:
fwrite(&intmatrix, size, sizeof(int), bmp_ptr);
Note, that the block size is sizeof(int), as your matrix doesn't contain single characters, but integers for each value. Depending on the content of your matrix, it might be a good idea to convert the values to COLORREF values (Check the RGB macro, which can be found in WinGDI.h, too)
I've rewritten and commented the answer from https://stackoverflow.com/a/2654860/586784. I hope you find it clear enough.
#include <cstddef>
#include <armadillo>
#include <map>
#include <cstdio>
#include <cassert>
///Just a tiny struct to bundle three values in range [0-255].
struct Color{
Color(unsigned char red, unsigned char green, unsigned char blue)
: red(red),green(green),blue(blue)
{}
///Defualt constructed Color() is black.
Color()
: red(0),green(0),blue(0)
{}
///Each color is represented by a combination of red, green, and blue.
unsigned char red,green,blue;
};
int main(int argc,char**argv)
{
///The width of the image. Replace with your own.
std::size_t w = 7;
///The height of the image. Replace with your own
std::size_t h = 8;
///http://arma.sourceforge.net/docs.html#Mat
///The Armadillo Linear Algebra Library Mat constructor is of the following
/// signature: mat(n_rows, n_cols).
arma::Mat<int> intmatrix(h,w);
///Fill out matrix, replace this with your own.
{
///Zero fill matrix
for(std::size_t i=0; i<h; ++i)
for(std::size_t j=0;j<w; ++j)
intmatrix(i,j) = 0;
intmatrix(0,3) = 1;
intmatrix(1,3) = 1;
intmatrix(2,2) = 6;
intmatrix(2,4) = 6;
intmatrix(3,2) = 4;
intmatrix(3,4) = 4;
intmatrix(4,1) = 6;
intmatrix(4,2) = 6;
intmatrix(4,3) = 6;
intmatrix(4,4) = 6;
intmatrix(4,5) = 6;
intmatrix(5,1) = 1;
intmatrix(5,2) = 1;
intmatrix(5,3) = 1;
intmatrix(5,4) = 1;
intmatrix(5,5) = 1;
intmatrix(6,0) = 4;
intmatrix(6,6) = 4;
intmatrix(7,0) = 6;
intmatrix(7,6) = 6;
}
///Integer to color associations. This is a map
///that records the meanings of the integers in the matrix.
///It associates a color with each integer.
std::map<int,Color> int2color;
///Fill out the color associations. Replace this with your own associations.
{
///When we see 0 in the matrix, we will use this color (red-ish).
int2color[0] = Color(255,0,0);
///When we see 0 in the matrix, we will use this color (green-ish).
int2color[1] = Color(0,255,0);
///When we see 0 in the matrix, we will use this color (blue-ish).
int2color[4] = Color(0,0,255);
///When we see 0 in the matrix, we will use this color (grey-ish).
int2color[6] = Color(60,60,60);
}
///The file size will consist of w*h pixels, each pixel will have an RGB,
/// where each color R,G,B is 1 byte, making the data part of the file to
/// be of size 3*w*h. In addition there is a header to the file which will
/// take of 54 bytes as we will see.
std::size_t filesize = 54 + 3*w*h;
///We make an array of 14 bytes to represent one part of the header.
///It is filled out with some default values, and we will fill in the
///rest momentarily.
unsigned char bmpfileheader[14] = {'B','M', 0,0,0,0, 0,0, 0,0, 54,0,0,0};
///The second part of the header is 40 bytes; again we fill it with some
///default values, and will fill in the rest soon.
unsigned char bmpinfoheader[40] = {40,0,0,0, 0,0,0,0, 0,0,0,0, 1,0, 24,0};
///We will now store the filesize,w,h into the header.
///We can't just write them to the file directly, because different platforms
///encode their integers in different ways. This is called "endianness"
///or "byte order". So we chop our integers up into bytes, and put them into
///the header byte-by-byte in the way we need to.
///Encode the least significant 8 bits of filesize into this byte.
///Because sizeof(unsigned char) is one byte, and one byte is eight bits,
///when filesize is casted to (unsigned char) only the least significant
///8 bits are kept and stored into the byte.
bmpfileheader[ 2] = (unsigned char)(filesize );
///... Now we shift filesize to the right 1 byte, meaning and trunctate
///that to its least significant 8 bits. This gets stored in the next
///byte.
bmpfileheader[ 3] = (unsigned char)(filesize>> 8);
///...
bmpfileheader[ 4] = (unsigned char)(filesize>>16);
///Encodes the most significant 8 bits of filesize into this byte.
bmpfileheader[ 5] = (unsigned char)(filesize>>24);
///Now we will store w (the width of the image) in the same way,
/// but into the byte [5-8] in bmpinfoheader.
bmpinfoheader[ 4] = (unsigned char)( w );
bmpinfoheader[ 5] = (unsigned char)( w>> 8);
bmpinfoheader[ 6] = (unsigned char)( w>>16);
bmpinfoheader[ 7] = (unsigned char)( w>>24);
///Now we will store h (the width of the image) in the same way,
/// but into the byte [9-12] in bmpinfoheader.
bmpinfoheader[ 8] = (unsigned char)( h );
bmpinfoheader[ 9] = (unsigned char)( h>> 8);
bmpinfoheader[10] = (unsigned char)( h>>16);
bmpinfoheader[11] = (unsigned char)( h>>24);
///Now we open the output file
FILE* f = fopen("img.bmp","wb");
///First write the bmpfileheader to the file. It is 14 bytes.
///The 1 means we are writing 14 elements of size 1.
///Remember, bmpfileheader is an array which is basically
///the same thing as saying it is a pointer to the first element
///in an array of contiguous elements. We can thus say:
///write 14 bytes, starting from the spot where bmpfileheader points
///to.
fwrite(bmpfileheader,1,14,f);
///Then write the bmpinfoheader, which is 40 bytes, in the same way.
fwrite(bmpinfoheader,1,40,f);
///Now we write the data.
///For each row (there are h rows), starting from the last, going
///up to the first.
///We iterate through the rows in reverse order here,
///apparently in the BMP format, the image
///is stored upside down.
for(std::size_t i=h-1; i != std::size_t(-1); --i)
{
///For each column in the row,
for(std::size_t j=0; j<w; ++j)
{
///We retreive the integer of the matrix at (i,j),
///and assert that there is a color defined for it.
assert (int2color.count(intmatrix(i,j)) != 0
&& "Integer in matrix not defined in int2color map");
///We somehow get the color for pixel (i,j).
///In our case, we get it from the intmatrix, and looking
///up the integer's color.
Color color = int2color[intmatrix(i,j)];
///Now the colors are written in reverse order: BGR
///We write the color using fwrite, by taking a pointer
///of the (unsigned char), which is the same thing as
///an array of length 1. Then we write the byte.
///First for blue,
fwrite(&color.blue,1,1,f);
///Same for green,
fwrite(&color.green,1,1,f);
///Finally red.
fwrite(&color.red,1,1,f);
}
///Now we do some padding, from 0-3 bytes, depending in the width.
unsigned char bmppad[3] = {0,0,0};
fwrite(bmppad,1,(4-(w*3)%4)%4,f);
}
///Free the file.
fclose(f);
return 0;
}
Is you problem seeing the matrix as an image or writing an image from your code ?
In the former case, just do as Ben Jackson said
In the later case, you want to pass the address of the data pointer of the Arm::Matrix, and using fwrite assumes that Arm::Matrix holds it's data as a contiguous memory array
[edit]
brief look at armadillo doc also tells that data is stored in column-major mode, but BMP assumes row-major mode, so your image will look flipped
[edit2]
Using Armadillo Matrix functions, it's even simpler
// assume A is a matrix
// and maxVal is the maximum int value in you matrix (you might scale it to maxVal = 255)
std::ofstream outfile("name.pgm");
oufile << "P2 " << sd::endl << a.n_rows << " " << a.n_cols << std::endl << maxVal << std::endl;
outfile << a << std::endl;
outfile.close();