C++ fast way to save image from array of values - c++

Right now, I am using CImg.
I am unable to use OpenCV due to this issue.
My CImg code looks like this:
cimg_library::CImg<float> img(512,512);
cimg_forXYC(img,x,y,c) { img(x,y,c) = (array[x][y]); } //array contains all float values between 0/1
img.save(save.c_str()); //taking a lot of time
By using clocks I was able to determine that the first step, the for loop takes 0-0.01 seconds. However, the second step, the saving of the image, takes 0.06 seconds, which is way too long due to the amount of images I have.
I am saving as bitmaps.
Is there any faster way to accomplish the same things (creating an image from an array of values and save) in C++?

Here is a small function that will save your image in pgm format, which most things can read and is dead simple. It requires your compiler support C++11, which most do. It's also hard-coded to 512x512 images.
#include <fstream>
#include <string>
#include <cmath>
#include <cstdint>
void save_image(const ::std::string &name, float img_vals[][512])
{
using ::std::string;
using ::std::ios;
using ::std::ofstream;
typedef unsigned char pixval_t;
auto float_to_pixval = [](float img_val) -> pixval_t {
int tmpval = static_cast<int>(::std::floor(256 * img_val));
if (tmpval < 0) {
return 0u;
} else if (tmpval > 255) {
return 255u;
} else {
return tmpval & 0xffu;
}
};
auto as_pgm = [](const string &name) -> string {
if (! ((name.length() >= 4)
&& (name.substr(name.length() - 4, 4) == ".pgm")))
{
return name + ".pgm";
} else {
return name;
}
};
ofstream out(as_pgm(name), ios::binary | ios::out | ios::trunc);
out << "P5\n512 512\n255\n";
for (int x = 0; x < 512; ++x) {
for (int y = 0; y < 512; ++y) {
const pixval_t pixval = float_to_pixval(img_vals[x][y]);
const char outpv = static_cast<const char>(pixval);
out.write(&outpv, 1);
}
}
}

In a similar vein to #Omnifarious's answer, there is an extremely simple format (also based on NetPBM concepts) for float data such as yours. It is called PFM and is documented here.
The benefit is that both CImg and ImageMagick are able to read and write the format without any additional libraries, and without you needing to write any code! An additional benefit is that you retain the full tonal range of your floats, rather than just 256 steps. On the downside, you do need the full 4 bytes per pixel rather than 1 byte.
So, your code would become:
CImg<float> img(512,512);
cimg_forXYC(img,x,y,c) { img(x,y,c) = (array[x][y]); }
img.save_pfm("filename.pfm");
I benchmarked this by creating 10,000 images and saving them to disk with the following code:
#include <iostream>
#include <cstdlib>
#define cimg_display 0 // No need for X11 stuff
#include "CImg.h"
using namespace cimg_library;
using namespace std;
#define W 512
#define H 512
#define N 10000
int main() {
// Create and initialise float image with radial gradient
cimg_library::CImg<float> img(W,H);
cimg_forXY(img,x,y) {img(x,y) = hypot((float)(W/2-x),(float)(H/2-y)); }
char filename[128];
for(int i=0;i<N;i++){
sprintf(filename,"f-%06d.pfm",i);
img.save_pfm(filename);
}
}
It runs in 21.8 seconds, meaning 2.1 ms per image (0.002s).
As I mentioned earlier, ImageMagick is also able to handle PFM format, so you can then use GNU Parallel and ImageMagick mogrify to convert those images to JPEG:
parallel -X mogrify -format jpg -auto-level ::: *pfm
For the original 10,000 images, that takes 22 seconds, or 2.2 ms/image.

Related

Application for stitching bmp images together. Help needed

I'm supposed to create some code to stitch together N bmp images found in a folder. At the moment I just want to add the images together, side by side, don't care yet about common regions in them (I'm referring to how panoramic images are made).
I have tried to use some examples online for different functions that i need, examples which I've partially understood. I'm currently stuck because I can't really figure out what's wrong.
The basis of the bmp.h file is this page:
https://solarianprogrammer.com/2018/11/19/cpp-reading-writing-bmp-images/
I'm attaching my code and a screenshot of the exception VS throws.
main:
#include "bmp.h"
#include <fstream>
#include <iostream>
#include <filesystem>
namespace fs = std::filesystem;
int main() {
int totalImages = 0;
int width = 0;
int height;
int count = 0;
std::string path = "imagini";
//Here i count the total number of images in the directory.
//I need this to know the width of the composed image that i have to produce.
for (const auto & entry : fs::directory_iterator(path))
totalImages++;
//Here i thought about going through the directory and finding out the width of the images inside it.
//I haven't managed to think of a better way to do this (which is probably why it doesn't work, i guess).
//Ideally, i would have taken one image from the directory and multiply it's width
//by the total number of images in the said directory, thus getting the width of the resulting image i need.
for (auto& p : fs::directory_iterator(path))
{
std::string s = p.path().string();
const char* imageName = s.c_str();
BMP image(imageName);
width = width + image.bmp_info_header.width;
height = image.bmp_info_header.height;
}
BMP finalImage(width, height);
//Finally, I was going to pass the directory again, and for each image inside of it, i would call
//the create_data function that i wrote in bmp.h.
for (auto& p : fs::directory_iterator(path))
{
count++;
std::string s = p.path().string();
const char* imageName = s.c_str();
BMP image(imageName);
//I use count to point out which image the iterator is currently at.
finalImage.create_data(count, image, totalImages);
}
//Here i would write the finalImage to a bmp image.
finalImage.write("textura.bmp");
}
bmp.h (I have only added the part I wrote, the rest of the code is found at the link I've provided above):
// This is where I try to copy the pixel RGBA values from the image passed as parameter (from it's data vector) to my
// resulting image (it's data vector) .
// The math should be right, I've gone through it with pen&paper, but right now I can't test it because the code doesn't work for other reasons.
void create_data(int count, BMP image, int totalImages)
{
int w = image.bmp_info_header.width * 4;
int h = image.bmp_info_header.height;
int q = 0;
int channels = image.bmp_info_header.bit_count / 8;
int startJ = w * channels * (count - 1);
int finalI = w * channels * totalImages* (h - 1);
int incrementI = w * channels * totalImages;
for(int i = 0; i <= finalI; i+incrementI)
for (int j = i + startJ; j < i + startJ + w * channels; j+4)
{
data[j] =image.data[q];
data[j+1]=image.data[q+1];
data[j+2]=image.data[q+2];
data[j+3]=image.data[q+3];
q = q + 4;
}
}
Error I get: https://imgur.com/7fq9BH4
This is the first time I post a question, I've only looked up answers here. If I don't provide enough info to my problem, or something I've done is not ok I apologize.
Also, English is my second language, so I hope I got my points across pretty clear.
EDIT: Since I forgot to mention, I would like to do this code without using external libraries like OpenCV or ImageMagick.

GraphicsMagick TTF Font Performance

I am using GraphicsMagick in a C++ library in order to create rasterized output, which mainly consists out of text.
I am doing something like this:
void gfx_writer::add_text(Magick::Image& img) const
{
using namespace Magick;
const unsigned x = // just a position;
const unsigned y_title = // just a position;
const unsigned y_heading = // just a position;
const unsigned y_value = // just a position;
img.strokeColor("transparent");
img.fillColor("black");
img.font(font_title_);
img.fontPointsize(font_size_title_);
img.draw(DrawableText{static_cast<double>(x), static_cast<double>(y_title), "a text"});
img.font(font_heading_);
img.fontPointsize(font_size_heading_);
img.draw(DrawableText{static_cast<double>(x), static_cast<double>(y_heading), "another text"});
img.font(font_value_);
img.fontPointsize(font_size_value_);
img.draw(DrawableText{static_cast<double>(x), static_cast<double>(y_value), "third text"});
}
Whereas font_title_, font_heading_ and font_value_ are paths to the TTF files.
This is done more than once and I experience rather bad performance. When I have a look at what happens using Sysinternals Process Monitor I see that the TTF files are read over and over again. So my questions are:
are my observations correct, that the TTF files are read each time img.font(...) is called?
is there a way to somehow cache the font using GraphicsMagick OR to provided something else than just the path to the TTF file?
any other thing I am missing?
Note: This answer uses ImageMagick's Magick++ library, and may have minor portability issues with GraphicsMagick, but the underlying solution is the same.
are my observations correct, that the TTF files are read each time img.font(...) is called?
Yes, the TTF font is reloaded each time. One option is to install the fonts in the system, and call the font-family constructor.
DrawableFont ( const std::string &family_,
StyleType style_,
const unsigned long weight_,
StretchType stretch_ );
Most systems have some sort of font caching system that would allow quicker access, but not really noticeable on modern hardware.
any other thing I am missing?
Try building a graphical context, and only call Magick::Image.draw once. Remember that the Drawable... calls are only wrapping MVG statements, and creating a std::list<Drawable> allows you to build complex vectors. Only when the draw method consumes the draw commands is when the TTF will be loaded, so its key to prepare all the drawing commands ahead of time.
Let's start by rewriting the code you provided (and I'm taking a degree of liberty here).
#include <Magick++.h>
const char * font_title_ = "fonts/OpenSans-Regular.ttf";
const char * font_heading_ = "fonts/LiberationMono-Regular.ttf";
const char * font_value_ = "fonts/sansation.ttf";
double font_size_title_ = 32;
double font_size_heading_ = 24;
double font_size_value_ = 16;
void gfx_writer_add_text(Magick::Image& img)
{
using namespace Magick;
double x = 10.0;
double y_title = 10;
double y_heading = 20.0;
double y_value = 30.0;
img.strokeColor("transparent");
img.fillColor("black");
img.font(font_title_);
img.fontPointsize(font_size_title_);
img.draw(DrawableText{x, y_title, "a text"});
img.font(font_heading_);
img.fontPointsize(font_size_heading_);
img.draw(DrawableText{x, y_heading, "another text"});
img.font(font_value_);
img.fontPointsize(font_size_value_);
img.draw(DrawableText{x, y_value, "third text"});
}
int main()
{
Magick::Image img("wizard:");
gfx_writer_add_text(img);
gfx_writer_add_text(img);
gfx_writer_add_text(img);
img.write("output.png");
}
I can compile and benchmark the run time. I get the following times:
$ time ./original.o
real 0m5.061s
user 0m0.094s
sys 0m0.029s
Refactoring the code to use a drawing context, and only call Magick::Image.draw once.
#include <Magick++.h>
#include <list>
const char * font_title_ = "fonts/OpenSans-Regular.ttf";
const char * font_heading_ = "fonts/LiberationMono-Regular.ttf";
const char * font_value_ = "fonts/sansation.ttf";
double font_size_title_ = 32;
double font_size_heading_ = 24;
double font_size_value_ = 16;
void gfx_writer_add_text(Magick::Image& img)
{
using namespace Magick;
double x = 10.0;
double y_title = 10;
double y_heading = 20.0;
double y_value = 30.0;
std::list<Drawable> ctx;
ctx.push_back(DrawableStrokeColor("transparent"));
ctx.push_back(DrawableFillColor("black"));
/* TITLE */
ctx.push_back(DrawablePushGraphicContext());
ctx.push_back(DrawableFont(font_title_);
ctx.push_back(DrawablePointSize(font_size_title_));
ctx.push_back(DrawableText{x, y_title, "a text"});
ctx.push_back(DrawablePopGraphicContext());
/* HEADING */
ctx.push_back(DrawablePushGraphicContext());
ctx.push_back(DrawableFont(font_heading_));
ctx.push_back(DrawablePointSize(font_size_heading_));
ctx.push_back(DrawableText{x, y_heading, "another text"});
ctx.push_back(DrawablePopGraphicContext());
/* Value */
ctx.push_back(DrawablePushGraphicContext());
ctx.push_back(DrawableFont(font_value_));
ctx.push_back(DrawablePointSize(font_size_value_));
ctx.push_back(DrawableText{x, y_value, "third text"});
ctx.push_back(DrawablePopGraphicContext());
img.draw(ctx);
}
int main()
{
Magick::Image img("wizard:");
gfx_writer_add_text(img);
gfx_writer_add_text(img);
gfx_writer_add_text(img);
img.write("output2.png");
}
And the benchmark times are slightly better.
$ time ./with_context.o
real 0m0.106s
user 0m0.090s
sys 0m0.012s
This is done more than once and I experience rather bad performance.
Worth taking a step back, and asking: "How can a refactor my solution to only draw at the last possible moment?".

OpenCV vs byte array

I am working on a simple C++ image processing application and deciding whether to use OpenCV for loading the image and accessing individual pixels.
My current approach is to simply load the image using fopen, reading the 54 byte header and load the rest of the bytes in a char* array.
To access a specific pixel I use
long q = (long*)(bmpData + x*3 + (bmpSize.height - y - 1) * bmpSize.stride);
To perform a simple color check, for ex. "is blue?"
if (((long*)q | 0xFF000000) == 0xFFFF0000) //for some reason RGB is reversed to BGR
//do something here
Is OpenCV any faster considering all the function calls, parsing, etc.?
Bitmap file header is actually 54 bytes and you can't skip it. You have to read it to find the width, height, bitcount... calculate padding if necessary... and other information.
Depending on how the file is opened, OpenCV will read the header and reads the pixels directly in to a buffer. The only change is that the rows are flipped so the image is right side up.
cv::Mat mat = cv::imread("filename.bmp", CV_LOAD_IMAGE_COLOR);
uint8_t* data = (uint8_t*)mat.data;
The header checks and the small changes made by OpenCV will not significantly affect performance. The bottle neck is mainly in reading the file from the disk. The change in performance will be difficult to measure, unless you are doing a very specific task, for example you want only 3 bytes in a very large file, and you don't want to read the entire file.
OpenCV is overkill for this task, so you may choose other libraries for example CImg as suggested in comments. If you use smaller libraries they load faster, it might be noticeable when your program starts.
The following code is a test run on Windows.
For a large 16MB bitmap file, the result is almost identical for opencv versus plain c++.
For a small 200kb bitmap file, the result is 0.00013 seconds to read in plain C++, and 0.00040 seconds for opencv. Note the plain c++ is not doing much beside reading the bytes.
class stopwatch
{
std::chrono::time_point<std::chrono::system_clock> time_start, time_end;
public:
stopwatch() { reset();}
void reset(){ time_start = std::chrono::system_clock::now(); }
void print(const char* title)
{
time_end = std::chrono::system_clock::now();
std::chrono::duration<double> diff = time_end - time_start;
if(title) std::cout << title;
std::cout << diff.count() << "\n";
}
};
int main()
{
const char* filename = "filename.bmp";
//I use `fake` to prevent the compiler from over-optimization
//and skipping the whole loop. But it may not be necessary here
int fake = 0;
//open the file 100 times
int count = 100;
stopwatch sw;
for(int i = 0; i < count; i++)
{
//plain c++
std::ifstream fin(filename, std::ios::binary);
fin.seekg(0, std::ios::end);
int filesize = (int)fin.tellg();
fin.seekg(0, std::ios::beg);
std::vector<uint8_t> pixels(filesize - 54);
BITMAPFILEHEADER hd;
BITMAPINFOHEADER bi;
fin.read((char*)&hd, sizeof(hd));
fin.read((char*)&bi, sizeof(bi));
fin.read((char*)pixels.data(), pixels.size());
fake += pixels[i];
}
sw.print("time fstream: ");
sw.reset();
for(int i = 0; i < count; i++)
{
//opencv:
cv::Mat mat = cv::imread(filename, CV_LOAD_IMAGE_COLOR);
uint8_t* pixels = (uint8_t*)mat.data;
fake += pixels[i];
}
sw.print("time opencv: ");
printf("show some fake calculation: %d\n", fake);
return 0;
}

C++ create png/bitmap from array of numbers

So i found this link regarding my question, but it is for c#
Create a PNG from an array of bytes
I have a variable int array of numbers.
i will call it "pix[ ]"
for now it can be any size from 3 to 256, later possibly bigger.
What i want to do now, is to convert it into a pixel image.
I am still a noobin c++ so pleas excuse me.
I tried to download some libaries that make working with libpng easier, but they do not seem to be working (ubuntu, code::blocks)
So i have questions in the following:
1) how do you create a new bitmap (which libaries, which command)?
2) how do i fill it with information from "pix[ ]" ?
3) how do i save it?
if it is a repost of a question i am happy about a link also ;)
Here is what i worked out so far, thanks for your help.
int main(){
FILE *imageFile;
int x,y,pixel,height=2,width=3;
imageFile=fopen("image.pgm","wb");
if(imageFile==NULL){
perror("ERROR: Cannot open output file");
exit(EXIT_FAILURE);
}
fprintf(imageFile,"P3\n"); // P3 filetype
fprintf(imageFile,"%d %d\n",width,height); // dimensions
fprintf(imageFile,"255\n"); // Max pixel
int pix[100] {200,200,200, 100,100,100, 0,0,0, 255,0,0, 0,255,0, 0,0,255};
fwrite(pix,1,18,imageFile);
fclose(imageFile);
}
i have not fully understood what it does. i can open the output image, but it is not a correct representation of the Array.
If i change things around, for example making a 2 dimensional array, then the image viewer tells me "expected an integer" and doesn't show me an image.
So far so good.
As i have the array before the image i created a function aufrunden to round up to the next int number because i want to create a square image.
int aufrunden (double h)
{
int i =h;
if (h-i == 0)
{
return i;
}
else
{
i = h+1;
return i;
}
}
This function is used in the creation of the image.
If the image is bigger than the information the array provides like this (a is the length of th array)
double h;
h= sqrt(a/3.0);
int i = aufrunden(h);
FILE *imageFile;
int height=i,width=i;
It might happen now, that the array is a=24 long. aufrunden makes the image 3x3 so it has 27 values...meaning it is missing the values for 1 pixel.
Or worse it is only a=23 long. also creating a 3x3 image.
What will fwrite(pix,1,18,imageFile); write in those pixels for information? It would be best if the remaing values are just 0.
*edit never mind, i will just add 0 to the end of the array until it is filling up the whole square...sorry
Consider using a Netpbm format (pbm, pgm, or ppm).
These images are extremely simple text files that you can write without any special libraries. Then use some third-party software such as ImageMagick, GraphicsMagick, or pnmtopng to convert your image to PNG format. Here is a wiki article describing the Netpbm format.
Here's a simple PPM image:
P3 2 3 255
0 0 0 255 255 255
255 0 0 0 255 255
100 100 100 200 200 200
The first line contains "P3" (the "magic number identifying it as a text-PPM), 2 (width), 3 (height), 255 (maximum intensity).
The second line contains the two RGB pixels for the top row.
The third and fourth lines each contain the two RGB pixels for rows 2 and 3.
Use a larger number for maximum intensity (e.g. 1024) if you need a larger range of intensities, up to 65535.
Edited by Mark Setchell beyond this point - so I am the guilty party!
The image looks like this (when the six pixels are enlarged):
The ImageMagick command to convert, and enlarge, is like this:
convert image.ppm -scale 400x result.png
If ImageMagick is a bit heavyweight, or difficult to install you can more simply use the NetPBM tools (from here) like this (it's a single precompiled binary)
pnmtopng image.ppm > result.png
If, as it seems, you have got Magick++ and are happy to use that, you can write your code in C/C++ like this:
////////////////////////////////////////////////////////////////////////////////
// sample.cpp
// Mark Setchell
//
// ImageMagick Magick++ sample code
//
// Compile with:
// g++ sample.cpp -o sample $(Magick++-config --cppflags --cxxflags --ldflags --libs)
////////////////////////////////////////////////////////////////////////////////
#include <Magick++.h>
#include <iostream>
using namespace std;
using namespace Magick;
int main(int argc,char **argv)
{
unsigned char pix[]={200,200,200, 100,100,100, 0,0,0, 255,0,0, 0,255,0, 0,0,255};
// Initialise ImageMagick library
InitializeMagick(*argv);
// Create Image object and read in from pixel data above
Image image;
image.read(2,3,"RGB",CharPixel,pix);
// Write the image to a file - change extension if you want a GIF or JPEG
image.write("result.png");
}
You are not far off - well done for trying! As far as I can see, you only had a couple of mistakes:
You had P3 where you would actually need P6 if writing in binary.
You were using int type for your data, whereas you need to be using unsigned char for 8-bit data.
You had the width and height interchanged.
You were using the PGM extension which is for Portable Grey Maps, whereas your data is colour, so you need to use the PPM extension which is for Portable Pix Map.
So, the working code looks like this:
#include <stdio.h>
#include <stdlib.h>
int main(){
FILE *imageFile;
int x,y,pixel,height=3,width=2;
imageFile=fopen("image.ppm","wb");
if(imageFile==NULL){
perror("ERROR: Cannot open output file");
exit(EXIT_FAILURE);
}
fprintf(imageFile,"P6\n"); // P6 filetype
fprintf(imageFile,"%d %d\n",width,height); // dimensions
fprintf(imageFile,"255\n"); // Max pixel
unsigned char pix[]={200,200,200, 100,100,100, 0,0,0, 255,0,0, 0,255,0, 0,0,255};
fwrite(pix,1,18,imageFile);
fclose(imageFile);
}
If you then run that, you can convert the resulting image to a nice big PNG with
convert image.ppm -scale 400x result.png
If you subsequently need 16-bit data, you would change the 255 to 65535, and store in an unsigned short array rather than unsigned char and when you come to the fwrite(), you would need to write double the number of bytes.
The code below will take an integer array of pixel colors as input and write it to a .bmp bitmap file or, in reverse, read a .bmp bitmap file and store its image contents as an int array. It only requires the <fstream> library. The input parameter path can be for example C:/path/to/your/image.bmp and data is formatted as data[x+y*width]=(red<<16)|(green<<8)|blue;, whereby red, green and blue are integers in the range 0-255 and the pixel position is (x,y).
#include <string>
#include <fstream>
using namespace std;
typedef unsigned int uint;
int* read_bmp(const string path, uint& width, uint& height) {
ifstream file(path, ios::in|ios::binary);
if(file.fail()) println("\rError: File \""+filename+"\" does not exist!");
uint w=0, h=0;
char header[54];
file.read(header, 54);
for(uint i=0; i<4; i++) {
w |= (header[18+i]&255)<<(8*i);
h |= (header[22+i]&255)<<(8*i);
}
const int pad=(4-(3*w)%4)%4, imgsize=(3*w+pad)*h;
char* img = new char[imgsize];
file.read(img, imgsize);
file.close();
int* data = new int[w*h];
for(uint y=0; y<h; y++) {
for(uint x=0; x<w; x++) {
const int i = 3*x+y*(3*w+pad);
data[x+(h-1-y)*w] = (img[i]&255)|(img[i+1]&255)<<8|(img[i+2]&255)<<16;
}
}
delete[] img;
width = w;
height = h;
return data;
}
void write_bmp(const string path, const uint width, const uint height, const int* const data) {
const int pad=(4-(3*width)%4)%4, filesize=54+(3*width+pad)*height; // horizontal line must be a multiple of 4 bytes long, header is 54 bytes
char header[54] = { 'B','M', 0,0,0,0, 0,0,0,0, 54,0,0,0, 40,0,0,0, 0,0,0,0, 0,0,0,0, 1,0,24,0 };
for(uint i=0; i<4; i++) {
header[ 2+i] = (char)((filesize>>(8*i))&255);
header[18+i] = (char)((width >>(8*i))&255);
header[22+i] = (char)((height >>(8*i))&255);
}
char* img = new char[filesize];
for(uint i=0; i<54; i++) img[i] = header[i];
for(uint y=0; y<height; y++) {
for(uint x=0; x<width; x++) {
const int color = data[x+(height-1-y)*width];
const int i = 54+3*x+y*(3*width+pad);
img[i ] = (char)( color &255);
img[i+1] = (char)((color>> 8)&255);
img[i+2] = (char)((color>>16)&255);
}
for(uint p=0; p<pad; p++) img[54+(3*width+p)+y*(3*width+pad)] = 0;
}
ofstream file(path, ios::out|ios::binary);
file.write(img, filesize);
file.close();
delete[] img;
}
The code snippet was inspired by https://stackoverflow.com/a/47785639/9178992
For .png images, use lodepng.cpp and lodepng.h:
#include <string>
#include <vector>
#include <fstream>
#include "lodepng.h"
using namespace std;
typedef unsigned int uint;
int* read_png(const string path, uint& width, uint& height) {
vector<uchar> img;
lodepng::decode(img, width, height, path, LCT_RGB);
int* data = new int[width*height];
for(uint i=0; i<width*height; i++) {
data[i] = img[3*i]<<16|img[3*i+1]<<8|img[3*i+2];
}
return data;
}
void write_png(const string path, const uint width, const uint height, const int* const data) {
uchar* img = new uchar[3*width*height];
for(uint i=0; i<width*height; i++) {
const int color = data[i];
img[3*i ] = (color>>16)&255;
img[3*i+1] = (color>> 8)&255;
img[3*i+2] = color &255;
}
lodepng::encode(path, img, width, height, LCT_RGB);
delete[] img;
}

Faster algorithm to check the colors in a image

Supposing I am given an image of 2048x2048 and i want to know the total number of colors present in the image, what is the fastest possible algorithm? I came up with two algorithm but they are slow.
Algorithm 1:
Compare the current pixel an the next pixel and if they are different
Check a temporary variable, which contains all the detected colors, to see if the color is present or not
If not present add it to the array(List) and increment noOfColors.
This Algorithm works but is slow. For a 1600x1200 pixels image it takes around 3 sec.
Algorithm 2:
The obvious method of checking the each pixel with all other pixels and recording the no of occurences of the color and incrementing the count. This is very very slow, almost like a hung app. So is there any better approach? I need all the pixel info.
You could use std::set (or std::unordered_set), and simply do a single loop though the pixels, adding the colors to the set. Then the number of colors is the size of the set.
Well, this is suited for parallelization. Split the image in several parts and execute the algorithm for each part in a separate task. To avoid syncing each should have its own storage for the unique colors. When all tasks are done, you aggregate the results.
DRAM is dirt cheap. Use brute force. Fill a tab, count.
On a core2duo # 3.0GHz :
0.35secs for 4096x4096 32 bits rgb
0.20secs after some trivial parallelization (I do know nothing of omp)
However, if you are to use 64bit rgb (one channel = 16 bits) it is another question (not enough memory).
You shall probably need a good hash table function.
Using random pixels, same size takes 10 secs.
Remark: at 0.15 secs, the std::bitset<> solution is faster (it gets slower trivially parallelized !).
Solution, c++11
#include <vector>
#include <random>
#include <iostream>
#include <boost/chrono.hpp>
#define _16M 256*256*256
typedef union {
struct { unsigned char r,g,b,n ; } r_g_b_n ;
unsigned char rgb[4] ;
unsigned i_rgb;
} RGB ;
RGB make_RGB(unsigned char r, unsigned char g , unsigned char b) {
RGB res;
res.r_g_b_n.r = r;
res.r_g_b_n.g = g;
res.r_g_b_n.b = b;
res.r_g_b_n.n = 0;
return res;
}
static_assert(sizeof(RGB)==4,"bad RGB size not 4");
static_assert(sizeof(unsigned)==4,"bad i_RGB size not 4");
struct Image
{
Image (unsigned M, unsigned N) : M_(M) , N_(N) , v_(M*N) {}
const RGB* tab() const {return & v_[0] ; }
RGB* tab() {return & v_[0] ; }
unsigned M_ , N_;
std::vector<RGB> v_;
};
void FillRandom(Image & im) {
std::uniform_int_distribution<unsigned> rnd(0,_16M-1);
std::mt19937 rng;
const int N = im.M_ * im.N_;
RGB* tab = im.tab();
for (int i=0; i<N; i++) {
unsigned r = rnd(rng) ;
*tab++ = make_RGB( (r & 0xFF) , (r>>8 & 0xFF), (r>>16 & 0xFF) ) ;
}
}
size_t Count(const Image & im) {
const int N = im.M_ * im.N_;
std::vector<char> count(_16M,0);
const RGB* tab = im.tab();
#pragma omp parallel
{
#pragma omp for
for (int i=0; i<N; i++) {
count[ tab->i_rgb ] = 1 ;
tab++;
}
}
size_t nColors = 0 ;
#pragma omp parallel
{
#pragma omp for
for (int i = 0 ; i<_16M; i++) nColors += count[i];
}
return nColors;
}
int main() {
Image im(4096,4096);
FillRandom(im);
typedef boost::chrono::high_resolution_clock hrc;
auto start = hrc::now();
std::cout << " # colors " << Count(im) << std::endl ;
boost::chrono::duration<double> sec = hrc::now() - start;
std::cout << " took " << sec.count() << " seconds\n";
return 0;
}
The only feasible algorithm here is building a sort of a histogram of the image colors. The only difference in your case is that instead of calculating the population of each color you need just to know if it's zero or not.
Depending on which color space you work, you may use either an std::set to tag existing colors (as Joachim Pileborg suggested), or just use something like std::bitset, which is obviously faster. This depends on how much distinct colors exist in your color-space.
Also, like Marius Bancila noted, this procedure is a perfect match for parallelization. Calculated the histogram-like data for image parts, and then merge it. Naturally the image division should be based on its memory partition, not the geometric properties. In simple words - split the image vertically (by batches of scan lines), not horizontally.
And, if possible, you should either use some low-level library/code to run through pixels, or try to write your own. At least you must obtain a pointer to scan line and run on its pixels in a batch, rather than doing something like GetPixel for each pixel.
The point, here, is that the ideal representation of an image as 2D array of colors is not the one that happens the way the image is stored on memory (color components can be arranged in "planes", there could be "padding" etc. So getting the pixels using a GetPixel-like function may take time.
The question, then, may even be somehow meaningless if the image is not the result of a "vectorial draw": think to a photograph: between two nearby "greens" you find all the shade of green, so the colors -in this case- are no more no less the ones supported by the encoding of the image itself (2^24, or 256, or 16 or ...), so, unless you are interested on the color distribution (how differently used they are), just counting them makes very few sense.
A workaround can be:
Create an in-memory bitmap having pixel in a "single plane format"
Blit your image into that bitmap using BitBlt or similar (this let the OS to make pixel
conversion from the GPU,if any)
Get the bitmap-bits (this lets you
access the stored values)
Play your "counting algorithm" (whatever
it is) onto those values.
Note that step 1 and 2 can be avoided if you already know that the image is already in planar format.
If you have a multicore system, step 4 can also be assigned to different threads, each working part of the image.
You can use bitset which allows you to set individual bits and has a count function.
You have a bit for each colour, there are 256 values for each of RGB, so that's 256*256*256 bits (16,777,216 colours). The bitset will use a byte for every 8 bits so it will use 2MB.
Use the pixel colour as an index into the bitset:
bitset<256*256*256> colours;
for(int pixel: pixels) {
colours[pixel] = true;
}
colours.count();
This has linear complexity.
Late comer to this answer, but could not help it since this algorithm is brutally fast, developed about 2 or more decades ago, when it really mattered.
3-D Lookup Table Color Matching
http://www.ddj.com/cpp/184403257
Basically, it creates a 3d color loop up table and the search is very fast, I've done some modifications to suit my purpose for image binarization, so I reduced the color space from ff ff ff to f f f, and it's even 10 times faster. As it is right out of the box, I haven't found anything even close, including hash tables.
char * creatematcharray(struct rgb_color *palette, int palettesize)
{
int rval=16, gval=16, bval=16, len, r, g, b;
char *taken, *match, *same;
int i, set, sqstep, tp, maxtp, *entryr, *entryg, *entryb;
char *table;
len=rval*gval*bval;
// Prepare table buffers:
size_t size_of_table = len*sizeof(char);
table=(char *)malloc(size_of_table);
if (table==nullptr) return nullptr;
// Select colors to use for fill:
set=0;
size_t size_of_taken = (palettesize * sizeof(int) * 3) +
(palettesize*sizeof(char)) + (len * sizeof(char));
taken=(char *)malloc(size_of_taken);
same=taken + (len * sizeof(char));
entryr=(int*)(same + (palettesize * sizeof(char)));
entryg=entryr + palettesize;
entryb=entryg + palettesize;
if (taken==nullptr)
{
free((void *)table);
return nullptr;
}
std::memset((void *)taken, 0, len * sizeof(char));
// std::cout << "sizes: " << size_of_table << " " << size_of_taken << std::endl;
match=table;
for (i=0; i<palettesize; i++)
{
same[i]=0;
// Compute 3d-table coordinates of palette rgb color:
r=palette[i].r&0x0f, g=palette[i].g&0x0f, b=palette[i].b&0x0f;
// Put color in position:
if (taken[b*rval*gval+g*rval+r]==0) set++;
else same[match[b*rval*gval+g*rval+r]]=1;
match[b*rval*gval+g*rval+r]=i;
taken[b*rval*gval+g*rval+r]=1;
entryr[i]=r; entryg[i]=g; entryb[i]=b;
}
// ### Fill match_array by steps: ###
for (set=len-set, sqstep=1; set>0; sqstep++)
{
for (i=0; i<palettesize && set>0; i++)
if (same[i]==0)
{
// Fill all six sides of incremented cube (by pairs, 3 loops):
for (b=entryb[i]-sqstep; b<=entryb[i]+sqstep; b+=sqstep*2)
if (b>=0 && b<bval)
for (r=entryr[i]-sqstep; r<=entryr[i]+sqstep; r++)
if (r>=0 && r<rval)
{ // Draw one 3d line:
tp=b*rval*gval+(entryg[i]-sqstep)*rval+r;
maxtp=b*rval*gval+(entryg[i]+sqstep)*rval+r;
if (tp<b*rval*gval+0*rval+r)
tp=b*rval*gval+0*rval+r;
if (maxtp>b*rval*gval+(gval-1)*rval+r)
maxtp=b*rval*gval+(gval-1)*rval+r;
for (; tp<=maxtp; tp+=rval)
if (!taken[tp])
taken[tp]=1, match[tp]=i, set--;
}
for (g=entryg[i]-sqstep; g<=entryg[i]+sqstep; g+=sqstep*2)
if (g>=0 && g<gval)
for (b=entryb[i]-sqstep; b<=entryb[i]+sqstep; b++)
if (b>=0 && b<bval)
{ // Draw one 3d line:
tp=b*rval*gval+g*rval+(entryr[i]-sqstep);
maxtp=b*rval*gval+g*rval+(entryr[i]+sqstep);
if (tp<b*rval*gval+g*rval+0)
tp=b*rval*gval+g*rval+0;
if (maxtp>b*rval*gval+g*rval+(rval-1))
maxtp=b*rval*gval+g*rval+(rval-1);
for (; tp<=maxtp; tp++)
if (!taken[tp])
taken[tp]=1, match[tp]=i, set--;
}
for (r=entryr[i]-sqstep; r<=entryr[i]+sqstep; r+=sqstep*2)
if (r>=0 && r<rval)
for (g=entryg[i]-sqstep; g<=entryg[i]+sqstep; g++)
if (g>=0 && g<gval)
{ // Draw one 3d line:
tp=(entryb[i]-sqstep)*rval*gval+g*rval+r;
maxtp=(entryb[i]+sqstep)*rval*gval+g*rval+r;
if (tp<0*rval*gval+g*rval+r)
tp=0*rval*gval+g*rval+r;
if (maxtp>(bval-1)*rval*gval+g*rval+r)
maxtp=(bval-1)*rval*gval+g*rval+r;
for (; tp<=maxtp; tp+=rval*gval)
if (!taken[tp])
taken[tp]=1, match[tp]=i, set--;
}
}
}
free((void *)taken);`enter code here`
return table;
}
The answer: unordered_map
I use unordered_map, based on my testing.
You should test because your compiler / library may exhibit different performance Comment out #define USEHASH to use map instead.
On my machine, the vanilla unordered_map (a hash implementation) is about twice as fast as map. Inasmuch as different compilers, libraries can vary enormously, you must test to see which is better. In production, I build a fake image on first start of the app, run both algorithms on it and time them, save an indication of which one is faster, and then preferentially use that for all subsequent starts on that the machine. It's nit-picky, but hey, the user's time is valuable to them.
For a DSLR image with 12,106,244 pixels (about 12 megapixels, not a typo) and 11,857,131 distinct colors (also not a typo), map takes about 14 seconds, while unordered map takes about 7 seconds:
Test Code:
#define USEHASH 1
#ifdef USEHASH
#include <unordered_map>
#endif
size = im->xw * im->yw;
#ifdef USEHASH
// unordered_map is about twice as fast as map on my mac with qt5
// --------------------------------------------------------------
#include <unordered_map>
std::unordered_map<qint64, unsigned char> colors;
colors.reserve(size); // pre-allocate the hash space
#else
std::map<qint64, unsigned char> colors;
#endif
...use of either is in a loop where I build a 48-bit value of 0RGB in a 64-bit variable corresponding to the 16-bit RGB values of the image pixels, like so:
for (i=0; i<size; i++)
{
pel = BUILDPEL(i); // macro just shovels 0RGB into 64 bit pel from im
// You'd do the same for your image structure
// in whatever way is fastest for you
colors[pel] = 1;
}
cc = colors.size();
// time here: 14 secs for map, 7 secs for unordered_map with
// 12,106,244 pixels containing 11,857,131 colors on 12/24 core,
// 3 GHz, 64GB machine.