I have around 1000 float values in the range(0.0, 100.0) and I want to map these values into a color(RGB). What I did so far is to create a colormap with 1000 color(RGB) values, use the float values to index the colormap and get an RGB value.
But the problem is, I'm loosing precision since I cast float values into int before using them as indices to my colormap. What is the best way to do this float to rgb conversion?
EDIT:
color color_list[100];
float float_values[1000]
for(i = 0 to 999)
{
int colormap_idx = float_values[i]; // Note that the float is converted into an int
color current_color = color_list[colormap_idx];
}
The total number of RGB values you can have is 256^3. It would be nice if you could utilize all of them, but sometimes it can be hard to come up with a nice intuitive mapping. Since there are a total possible of 256^4 floats (more than possible RGB values) you will lose precision no matter what you do, but you can still do much, much better than what you currently.
I don't know exactly what you are doing with the pre-defined color map, but consider defining only a few intermediate colors that correspond to a few intermediate floating values and interpolating each input floating point value. In the code below, fsample and csample are your corresponding points. For example:
fsample[0] = 0.0 -> csample[0] = (0, 0, 0)
fsample[1] = 0.25 -> csample[1] = (0, 0, 100)
fsample[2] = 0.5 -> csample[2] = (0, 170, 170)
fsample[3] = 0.75 -> csample[3] = (170, 170, 0)
fsample[4] = 1.0 -> csample[4] = (255, 255, 255)
This will allow you to cover a lot more ground in RGB space with floats, allowing a higher precision conversion, while still giving you some power to flexibly define intermediate colors. This is a fairly common method to convert grayscale to color.
There are a few optimizations and error checks you can apply to this code, but I left it unoptimized for the sake of clarity:
int N = float_values.size();
color colormap[N];
for(i = 0 to N)
{
colormap[i] = RGBFromFloat(float_values[i], fsample, csample, num_samples);
}
color RGBFromFloat(float in, float fsample[], float csample[], num_samples)
{
color out;
// find the interval that the input 'in' lies in
// this is a simple search on an ordered array...
// consider replacing with a better algorithm for a large number of samples
for(i = 0 to num_samples-1)
{
if(fsample[i] =< in && in < fsample[i+1])
{
out = interpolate(fsample[i], fsample[i+1], csample[i], csample[i+1], in);
break;
}
}
return color;
}
color interpolate(float flow, float fhigh, color clow, color chigh, float in)
{
float t = (in-flow)/(fhigh-flow);
return clow*(1 - t) + chigh*t
}
I don't know if this is the best method (since you gave us no optimality criteria), but if by "I'm losing precision" you mean that once converted to int, you only have a maximum of 100 different color combinations, then you can do this:
// this code is C99
#define MAX_FLOAT_VAL 100.0
#define N_COLORS 2000
#define N_FLOAT_SAMPLES 1000
color color_list[N_COLORS];
float float_values[N_FLOAT_SAMPLES];
// the following loop must be placed in some function
for( int i = 0; i < N_FLOAT_SAMPLES; i++ )
{
// the following assignment will map
// linearly a float in the range [0 ... MAX_FLOAT_VAL]
// into an int in the range [0 ... (N_COLORS-1)]
int colormap_idx = (float_values[i] / MAX_FLOAT_VAL) * (N_COLORS - 1);
color current_color = color_list[colormap_idx];
// ... do something with current_color ...
}
Of course you still have to generate the entries in color_list with a suitable algorithm (I advice against doing that by hand :-). This is a whole different problem, since it involves more "degrees of freedom", since you try to map a 1-D space (the values of colormap_idx) to a 3-D space (the set of all the possible RGB triples).
P.S: the requirements you seem to have remind me of the computations needed to colorize a fractal like the graphic representation of the Mandelbrot's set.
Hope this helps.
Related
I want to make a mosaic photo with different window-size (that has been determined by user). This is just like a first draft of the code but I have problems to get the pixels and calculating averages. Then put the avarage value in each pixel and continue to the end. Even I get error to converting them of diffrent types: (Also the other part manufacturers a gray-scale image)
p.s: sorry that I am in the very first steps of learning image processing.
''' void CImageProcessingDoc::OnProcessMosaic()
{
if (m_pImage) {
DlgMosaicOption dlg;
if (dlg.DoModal() == IDOK) {
DWORD dwWindowSize = dlg.m_dwWindowSize;
DWORD width = m_pImage->GetWidth();
DWORD height = m_pImage->GetHeight();
RGBQUAD color;
RGBQUAD newcolor;
float X_step = width / dwWindowSize;
float Y_step = height / dwWindowSize;
int avg, pixel;
for (DWORD y = 0; y < dwWindowSize; y++) {
for (DWORD x = 0; x < dwWindowSize; x++) {
color = m_pImage->GetPixelColor(x, y);
(RGBQUAD) pixel = m_pImage->GetPixelColor(x, y);
avR += (int)(color.red(pixel);
avG += (int)(color.green(pixel);
avB += (int)(color.blue(pixel);
newcolor.rgbBlue = (BYTE)RGB2GRAY(color.rgbRed, color.rgbGreen, color.rgbBlue);
newcolor.rgbGreen = (BYTE)RGB2GRAY(color.rgbRed, color.rgbGreen, color.rgbBlue);
newcolor.rgbRed = (BYTE)RGB2GRAY(color.rgbRed, color.rgbGreen, color.rgbBlue);
m_pImage->SetPixelColor(x, y, newcolor);
}
}
}
}
} '''
Could anyone please help me to understand the problem?
I think you are mixing up spatial, spectral and temporal average here.
Spatial average
This is the operation of computing average of pixels over an area.
You have to compute eR = 1/N * (P0.R + P1.R + P2.R + P3.R + ...), eG = 1/N * (P0.G + P1.G + ...), eB = 1/N * (P0.B + P1.B + ...)
You'll get a pixel with as many color as there was in the input picture, but with limited spatial frequency, a picture computed like this will appear blurred, with no details
Spectral average
This is the operation of computing average of the components (spectrum) of each pixels.
You have to compute e = 1/3 * (P0.R + P0.G + P0.B)
You'll get a monochromic picture with the exact same spatial frequency as the initial picture.
Temporal average
While you haven't talked about it, this is for reference. The idea is to compute the average of each pixel, and each component for N pictures in a temporal sequence
This gives a kind of motion blurred picture.
Answer
If I understand your question correctly, you want spectral average to convert a RGB to the average grey value taken that grey = (R+G+B)/3.
Thus, you pixel loop should look like this:
for (DWORD y = 0; y < dwWindowSize; y++) {
for (DWORD x = 0; x < dwWindowSize; x++) {
color = m_pImage->GetPixelColor(x, y);
float avg = (color.rgbRed + color.rgbGreen + color.rgbBlue) / 3.f;
m_pImage->SetPixelColor(x, y, RGBQUAD(avg, avg, avg, 1.0f));
}
}
Please notice that converting non linear RGB (usually called sRGB) to luminance using the average is a poor formula for RGB to grayscale conversion. You should read about RGB to Lab* conversion (you are interested in L part only) or at least RGB to YUV (you are interested to Y part only).
If your question is about resizing the input picture, then you are not using the appropriate algorithm, what you want is called resampling.
It is good way to convert color from Linear space from 0.0 to 1.0 into sRGB space from 0 to 255 by using Lookup table in this manner?
Example, in Java:
byte[] table;
void initializeTable()
{
table = new byte[65536];
for(int i = 0; i < table.length; i++){
float Lin = i / (float) (table.length-1);
if(Lin<=0.0031308f)
table[i] = (byte)(255*(Lin*12.92f));
else
table[i] = (byte)(255*( (1+0.055f)*Math.pow(Lin,1/2.4f)-0.055f) );
}
}
int sRGB(float Linear/*in range 0..1*/) // Will return 0..255 integer sRGB
{
return 255 & table[(int)( Linear*(table.length-1) )];
}
Yes, that looks fine to me. You can probably use a smaller table, in fact.
If I were feeling pedantic, I'd say that your conversion from float to int always makes the value slightly darker. You can improve that by changing line 6:
float Lin = (i+0.5) / (float) (table.length-1);
or by changing line 16:
return 255 & table[(int)( Linear*(table.length-1) + 0.5 )];
but not both. Either fix centres the rounding error at zero. However, the rounding error still exists, and it is so small that it's not really worth mentioning, so I think your code is fine.
The previous answered question doesn't seem to answer my problem "Blocky" Perlin noise
I tried to simplify the most I could to make my code readable and understandable.
I don't use the permutation table, instead I use the mt19937 generator.
I use SFML
using namespace std;
using namespace sf;
typedef Vector2f Vec2;
Sprite spr;
Texture tx;
// dot product
float prod(Vec2 a, Vec2 b) { return a.x*b.x + a.y*b.y; }
// linear interpolation
float interp(float start,float end,float coef){return coef*(end-start)+start;}
// get the noise of a certain pixel, giving its relative value vector in the square with [0.0 1.0] values
float getnoise(Vec2&A, Vec2&B, Vec2&C, Vec2&D, Vec2 rel){
float
dot_a=prod(A ,Vec2(rel.x ,rel.y)),
dot_b=prod(B ,Vec2(rel.x-1 ,rel.y)),
dot_c=prod(C ,Vec2(rel.x ,rel.y-1)),
dot_d=prod(D ,Vec2(rel.x-1 ,rel.y-1));
return interp
(interp(dot_a,dot_b,rel.x),interp(dot_c,dot_d,rel.x),rel.y);
// return interp
// (interp(da,db,rel.x),interp(dc,dd,rel.x),rel.y);
}
// calculate the [0.0 1.0] relative value of a pixel
Vec2 getrel(int i, int j, float cellsize){
return Vec2
(float
(i // which pixel
-(i/int(cellsize))//which cell
*cellsize)// floor() equivalent
/cellsize,// [0,1] range
float(j-(j/int(cellsize))*cellsize)/cellsize
);
}
// generates an array of random float values
vector<float> seeded_rand_float(unsigned int seed, int many){
vector<float> ret;
std::mt19937 rr;
std::uniform_real_distribution<float> dist(0, 1.0);
rr.seed(seed);
for(int j = 0 ; j < many; ++j)
ret.push_back(dist(rr));
return ret;
}
// use above function to generate an array of random vectors with [0.0 1.0] values
vector<Vec2>seeded_rand_vec2(unsigned int seed, int many){
auto coeffs1 = seeded_rand_float(seed, many*2);
// auto coeffs2 = seeded_rand_float(seed+1, many); //bad choice !
vector<Vec2> pushere;
for(int i = 0; i < many; ++i)
pushere.push_back(Vec2(coeffs1[2*i],coeffs1[2*i+1]));
// pushere.push_back(Vec2(coeffs1[i],coeffs2[i]));
return pushere;
}
// here we make the perlin noise
void make_perlin()
{
int seed = 43;
int pixels = 400; // how many pixels
int divisions = 10; // cell squares
float cellsize = float(pixels)/divisions; // size of a cell
auto randv = seeded_rand_vec2(seed,(divisions+1)*(divisions+1));
// makes the vectors be in [-1.0 1.0] range
for(auto&a:randv)
a = a*2.0f-Vec2(1.f,1.f);
Image img;
img.create(pixels,pixels,Color(0,0,0));
for(int j=0;j<=pixels;++j)
{
for(int i=0;i<=pixels;++i)
{
int ii = int(i/cellsize); // cell index
int jj = int(j/cellsize);
// those are the nearest gradient vectors for the current pixel
Vec2
A = randv[divisions*jj +ii],
B = randv[divisions*jj +ii+1],
C = randv[divisions*(jj+1) +ii],
D = randv[divisions*(jj+1) +ii+1];
float val = getnoise(A,B,C,D,getrel(i,j,cellsize));
val = 255.f*(.5f * val + .7f);
img.setPixel(i,j,Color(val,val,val));
}
}
tx.loadFromImage(img);
spr.setPosition(Vec2(10,10));
spr.setTexture(tx);
};
Here are the results, I included the resulted gradients vector (I multiplied them by cellsize/2).
My question is why are there white artifacts, you can somehow see the squares...
PS: it has been solved, I posted the fixed source here http://pastebin.com/XHEpV2UP
Don't make the mistake of applying a smooth interp on the result instead of the coefficient. Normalizing vectors or adding an offset to avoid zeroes doesn't seem to improve anything. Here is the colorized result:
The human eye is sensitive to discontinuities in the spatial derivative of luminance (brightness). The linear interpolation you're using here is sufficient to make brightness continuous, but it does not not make the derivative of the brightness continuous.
Perlin recommends using eased interpolation to get smoother results. You could use 3*t^2 - 2*t^3 (as suggested in the linked presentation) right in your interpolation function. That should solve the immediate issue.
That would look something like
// interpolation
float linear(float start,float end,float coef){return coef*(end-start)+start;}
float poly(float coef){return 3*coef*coef - 2*coef*coef*coef;}
float interp(float start,float end,float coef){return linear(start, end, poly(coef));}
But note that evaluating a polynomial for every interpolation is needlessly expensive. Usually (including here) this noise is being evaluated over a grid of pixels, with squares being some integer (or rational) number of pixels large; this means that rel.x, rel.y, rel.x-1, and rel.y-1 are quantized to particular possible values. You can make a lookup table for values of the polynomial ahead of time at those values, replacing the "poly" function in the code snippet provided. This technique lets you use even smoother (e.g. degree 5) easing functions at very little additional cost.
Although Jerry is correct in his above answer (I would have simply commented above, but I'm still pretty new to StackOverflow and I have insufficient reputation to comment at the moment)...
And his solution of using:
(3*coef*coef) - (2*coef*coef*coef)
to smooth/curve the interpolation factor works.
The slightly better solution is to simplify the equation to:
(3 - (2*coef)) * coef*coef
the resulting curve is virtually identical (there are slight differences, but they are tiny), and there's 2 less multiplications (and still only a single subtraction) to do per interpolation. Resulting in less computational effort.
This reduction in computation could really add up over time, especially when using the noise function alot. For instance, if you start generating noise in more than 2 dimensions.
I am trying to create a very simple C++ program that given an argument in range [0-100] applies a low-pass filter to a grayscale image that should "compress" it proprotionally to the value of the given argument.
I am using the FFTW library.
I have some doubts about how I define the frequency threshold, cut. Is there any more effective way to define such value?
//fftw_complex *fft
//double[] magnitude
// . . .
int percent = 100;
if (percent < 0 || percent > 100) {
cerr << "Compression rate must be a value between 0 and 100." << endl;
return -1;
}
double cut =(double)(w*h) * ((double)percent / (double)100);
for (i = 0; i < (w * h); i++) {
magnitude[i] = sqrt(pow(fft[i][0], 2.0) + pow(fft[i][1], 2.0));
if (magnitude[i] < cut) {
fft[i][0] = 0.0;
fft[i][1] = 0.0;
}
}
Update1:
I've changed my code to this, but again I'm not sure this is a proper way to filter frequencies. The image is surely compressed, but non-square images are messed up and setting compression to 100% isn't the real maximum compression available (I can go up to ~140%).
Here you can find an image of what I see now.
int cX = w/2;
int cY = h/2;
cout<<"TEST "<<((double)percent/(double)100)*h<<endl;
for(i = 0; i<(w*h);i++){
int row = i/s;
int col = i%s;
int distance = sqrt((col-cX)*(col-cX)+(row-cY)*(row-cY));
if(distance<((double)percent/(double)100)*min(cX,cY)){
fft[i][0] = 0.0;
fft[i][1] = 0.0;
}
}
This is not a low-pass filter at all. A low-pass filter passes low frequencies, i.e. it removes fine details (blurring). You obviously need a 2D FFT for that.
This code just removes random bits, essentially.
[edit]
The new code looks a lot more like a low-pass filter. The 141% setting is expected: the diagonal of a square is sqrt(2)=1.41 times its side. Converting an index into a row/column pair should use the image width, not some random unexplained s.
I don't know where your zero frequency is located. That should be easy to spot (largest value) but it might be in (0,0) instead of (w/2,h/2)
I am trying to implement some function like below
For this I am trying to use Cubic interpolation and Catmull interpolation ( check both separately to compare the best result) , what i am not understanding is what impact these interpolation show on image and how we can get these points values where we clicked to set that curve ? and do we need to define the function these black points on the image separately ?
I am getting help from these resources
Source 1
Source 2
Approx the same focus
Edit
int main (int argc, const char** argv)
{
Mat input = imread ("E:\\img2.jpg");
for(int i=0 ; i<input.rows ; i++)
{
for (int p=0;p<input.cols;p++)
{
//for(int t=0; t<input.channels(); t++)
//{
input.at<cv::Vec3b>(i,p)[0] = 255*correction(input.at<cv::Vec3b>(i,p)[0]/255.0,ctrl,N); //B
input.at<cv::Vec3b>(i,p)[1] = 255*correction(input.at<cv::Vec3b>(i,p)[1]/255.0,ctrl,N); //G
input.at<cv::Vec3b>(i,p)[2] = 255*correction(input.at<cv::Vec3b>(i,p)[2]/255.0,ctrl,N); //R
//}
}
}
imshow("image" , input);
waitKey();
}
So if your control points are always on the same x coordinate
and linearly dispersed along whole range then you can do it like this:
//---------------------------------------------------------------------------
const int N=5; // number of control points (must be >= 4)
float ctrl[N]= // control points y values initiated with linear function y=x
{ // x value is index*1.0/(N-1)
0.00,
0.25,
0.50,
0.75,
1.00,
};
//---------------------------------------------------------------------------
float correction(float col,float *ctrl,int n)
{
float di=1.0/float(n-1);
int i0,i1,i2,i3;
float t,tt,ttt;
float a0,a1,a2,a3,d1,d2;
// find start control point
col*=float(n-1);
i1=col; col-=i1;
i0=i1-1; if (i0< 0) i0=0;
i2=i1+1; if (i2>=n) i2=n-1;
i3=i1+2; if (i3>=n) i3=n-1;
// compute interpolation coefficients
d1=0.5*(ctrl[i2]-ctrl[i0]);
d2=0.5*(ctrl[i3]-ctrl[i1]);
a0=ctrl[i1];
a1=d1;
a2=(3.0*(ctrl[i2]-ctrl[i1]))-(2.0*d1)-d2;
a3=d1+d2+(2.0*(-ctrl[i2]+ctrl[i1]));
// now interpolate new colro intensity
t=col; tt=t*t; ttt=tt*t;
t=a0+(a1*t)+(a2*tt)+(a3*ttt);
return t;
}
//---------------------------------------------------------------------------
It uses 4-point 1D interpolation cubic (from that link in my comment above) to get new color just do this:
new_col = correction(old_col,ctrl,N);
this is how it looks:
the green arrows shows derivation error (always only on start and end point of whole curve). It can be corrected by adding 2 more control points one before and one after all others ...
[Notes]
color range is < 0.0 , 1.0 > so if you need other then just multiply the result and divide the input ...
[edit1] the start/end derivations fixed a little
float correction(float col,float *ctrl,int n)
{
float di=1.0/float(n-1);
int i0,i1,i2,i3;
float t,tt,ttt;
float a0,a1,a2,a3,d1,d2;
// find start control point
col*=float(n-1);
i1=col; col-=i1;
i0=i1-1;
i2=i1+1; if (i2>=n) i2=n-1;
i3=i1+2;
// compute interpolation coefficients
if (i0>=0) d1=0.5*(ctrl[i2]-ctrl[i0]); else d1=ctrl[i2]-ctrl[i1];
if (i3< n) d2=0.5*(ctrl[i3]-ctrl[i1]); else d2=ctrl[i2]-ctrl[i1];
a0=ctrl[i1];
a1=d1;
a2=(3.0*(ctrl[i2]-ctrl[i1]))-(2.0*d1)-d2;
a3=d1+d2+(2.0*(-ctrl[i2]+ctrl[i1]));
// now interpolate new colro intensity
t=col; tt=t*t; ttt=tt*t;
t=a0+(a1*t)+(a2*tt)+(a3*ttt);
return t;
}
[edit2] just some clarification on the coefficients
they are all derived from this conditions:
y(t) = a0 + a1*t + a2*t*t + a3*t*t*t // direct value
y'(t) = a1 + 2*a2*t + 3*a3*t*t // first derivation
now you have points y0,y1,y2,y3 so I chose that y(0)=y1 and y(1)=y2 which gives c0 continuity (value is the same in the joint points between curves)
now I need c1 continuity so i add y'(0) must be the same as y'(1) from previous curve.
for y'(0) I choose avg direction between points y0,y1,y2
for y'(1) I choose avg direction between points y1,y2,y3
These are the same for the next/previous segments so it is enough. Now put it all together:
y(0) = y0 = a0 + a1*0 + a2*0*0 + a3*0*0*0
y(1) = y1 = a0 + a1*1 + a2*1*1 + a3*1*1*1
y'(0) = 0.5*(y2-y0) = a1 + 2*a2*0 + 3*a3*0*0
y'(1) = 0.5*(y3-y1) = a1 + 2*a2*1 + 3*a3*1*1
And solve this system of equtions (a0,a1,a2,a3 = ?). You will get what I have in source code above. If you need different properties of the curve then just make different equations ...
[edit3] usage
pic1=pic0; // copy source image to destination pic is mine image class ...
for (y=0;y<pic1.ys;y++) // go through all pixels
for (x=0;x<pic1.xs;x++)
{
float i;
// read, convert, write pixel
i=pic1.p[y][x].db[0]; i=255.0*correction(i/255.0,red control points,5); pic1.p[y][x].db[0]=i;
i=pic1.p[y][x].db[1]; i=255.0*correction(i/255.0,green control points,5); pic1.p[y][x].db[1]=i;
i=pic1.p[y][x].db[2]; i=255.0*correction(i/255.0,blue control points,5); pic1.p[y][x].db[2]=i;
}
On top there are control points per R,G,B. On bottom left is original image and on bottom right is corrected image.