View Full Version : Tweak: HSV - RGB conversion difference between programs
Ignus2
10th October 2006, 19:26
Hi!
I don't seem to get it:
I have read the docs of Tweak here (Google cached, as avisynth page is down):
http://www.google.com/search?q=cache:5rLWVuyPyXgJ:www.avisynth.org/Tweak+avisynth+skin&hl=en&gl=hu&ct=clnk&cd=1
I used the color picker of GIMP to select a color from the skin of the woman, then I got these values:
R: 200 (from 0-255 range)
G: 79 (from 0-255 range)
B: 34 (from 0-255 range)
GIMP also gave me HSV:
H: 16 degree (from 0-360 range)
S: 83% (from 0-100 range)
V: 78% (from 0-100 range)
Then I typed in the RGB values to Windows Paint color selector, and it gave me:
H: 11 (from 0-239 range)
S: 170 (from 0-240 range)
V: 110 (from 0-240 range)
As you can see, they are totally different, if I compare them. 83% according to GIMP should be around 200 for Windows Paint, no?
AND the REAL trouble is, that the docs of Tweak, also use TOTALLY DIFFERENT values for selecting the skin of the girl.
It uses Hue range 105-138 (from 0-360) or Sat range 45-75 (from 0-150) to achieve the selection, which is nowhere near the values what GIMP and Paint gives me.
Can someone clarify as what conversion does Tweak use, and what program can I use to select color values, which I can then supply to Tweak?
--
Thanks in advance,
B.
Wilbert
10th October 2006, 23:00
For YUV (input of Tweak) the hue and saturation are defined as:
theta = atan2(U-128, V-128) * 180.0/pi;
hue = (theta > 0) ? theta: 360+theta; (values mapped in [0,360])
sat = sqrt( (U-128)^2 + (V-128)^2 ) (see histogram docs)
with U,V in [0,255].
So convert:
R: 200 (from 0-255 range)
G: 79 (from 0-255 range)
B: 34 (from 0-255 range)
to YUV [16,235] values:
Y: 109
U: 82
V: 85
found by using the script:
BlankClip(color=$c84f10) # (200,79,34)
#RGBAdjust(analyze=true)
ConvertToYUY2()
ColorYUV(analyze=true)
and compute the hue and saturation.
You can also use Histogram(mode="color2") to find an estimate of the hue and saturation (using AviSynth v2.6) and fine tune it with Tweak(sat=0,starthue=xxx,endhue=xxx).
I hope this is enough to get you started.
Ignus2
1st November 2006, 01:00
For YUV (input of Tweak) the hue and saturation are defined as:
theta = atan2(U-128, V-128) * 180.0/pi;
Seems not right. Isn't it:
theta = atan2(V-128, U-128) * 180.0/pi;
? (After browsing through the avisynth source). If not, then I'm sorry for mentioning it.
Anyway, if I calculate the HSV according to the equations you have given (with the above correction), for a yellow color, I get around 170 for hue, which is fine (I tested with MaskHS).
Still, when I draw the color circle with Histogram(mode="color2"), the yellow is on the left side, at around 9 o'clock. And that is supposed to be 170 degrees. My question: where is 0 degree? At blue?
EDIT: I also tried the equations written on wikipedia: http://en.wikipedia.org/wiki/HSV_color_space and they also give different result:
R: 226, G: 232, B: 50
Y: 196, U: 49, V: 138 (from coloryuv analyze)
H: 172, S: 79, V: 232 (from above corrected equation)
H2: 61.978021978022, S2: 0.78448275862069, V2: 0.909803921568627 (from wikipedia)
As you can see, the S and V are OK (wikipedia interval is [0-1]), but H is not. Why is that? 110 seems a strange rotation, I expected 120, 180 or something like that.
--
Thanks in advance!
Wilbert
1st November 2006, 23:00
Seems not right. Isn't it:
theta = atan2(V-128, U-128) * 180.0/pi;
Yes, you are right.
Anyway, if I calculate the HSV according to the equations you have given (with the above correction), for a yellow color, I get around 170 for hue, which is fine (I tested with MaskHS).
Still, when I draw the color circle with Histogram(mode="color2"), the yellow is on the left side, at around 9 o'clock. And that is supposed to be 170 degrees. My question: where is 0 degree? At blue?
Yes. hue=0 (or blue) corresponds with U=255,V=128, which is mentioned in the histogram docs. So, yellow is (appr.) between 8 and 9 o'clock (or somewhere between 170 and 180 degrees).
EDIT: I also tried the equations written on wikipedia: http://en.wikipedia.org/wiki/HSV_color_space and they also give different result:
R: 226, G: 232, B: 50
Y: 196, U: 49, V: 138 (from coloryuv analyze)
H: 172, S: 79, V: 232 (from above corrected equation)
H2: 61.978021978022, S2: 0.78448275862069, V2: 0.909803921568627 (from wikipedia)
As you can see, the S and V are OK (wikipedia interval is [0-1]), but H is not. Why is that?
I don't know. I'm not sure whether they should be the same (i think that they are different definitions of the concept of hue).
110 seems a strange rotation, I expected 120, 180 or something like that.
Where did the 110 come from?
ps, your suggestion in the other thread is very nice. I will think about how to add it.
Ignus2
2nd November 2006, 00:21
110 is the offset of the wikipedia Hue and the avisynth Hue, that I seem to get for the same colors. See below.
An article about a color metric (color distance in particular) can be found here:
http://www.compuphase.com/cmetric.htm
I have tested the proposed metric (written a small plugin for it) and it seems to work nicely. Maybe you should take a look.
Also, take a look at the other thread for details about what I've been experimenting with.
(http://forum.doom9.org/showthread.php?t=117730)
Here is a Perl program I wrote for comparing color conversions from RGB to various others. I have taken code from avisynth source, and from wikipedia. I have tested them, and they work fine, yet their results are different. I'm curious why is that? HSV is supposed to be the same, no matter where you convert to HSV from, no?
#!/bin/perl -w
# Color converter
# Written by: Balazs OROSZI
use strict;
if (@ARGV < 3) {
print "Usage: rgb2hsv R G B\n";
print "(input range: 0-255)\n";
exit 1;
}
my ($R, $G, $B); # RGB [0-255]
my ($R1, $G1, $B1); # RGB [0-1]
my ($Y, $U, $V); # YUV
my ($H_a, $S_a, $V_a); # avisynth HSV
my ($H1, $S1, $V1); # HSV
my ($H2, $S2, $L); # HSL
my ($H3, $S3, $P); # HSP
$R = shift;
$G = shift;
$B = shift;
# RGB->YUV from avisynth with Rec601 coefficients
my $cyb = int(0.114*219/255*65536+0.5);
my $cyg = int(0.587*219/255*65536+0.5);
my $cyr = int(0.299*219/255*65536+0.5);
$Y = ($cyb*$B + $cyg*$G + $cyr*$R + 0x108000) >> 16;
my $scaled_y = ($Y*2 - 32) * int(255.0/219.0*32768+0.5);
my $b_y = (($B*2) << 15) - $scaled_y;
$U = ScaledPixelClip(($b_y / 2**15) * int(1/2.018*32768+0.5) + 0x800000);
my $r_y = (($R*2) << 15) - $scaled_y;
$V = ScaledPixelClip(($r_y / 2**15) * int(1/1.596*32768+0.5) + 0x800000);
# YUV->HSV from avisynth
my $PI = 3.1415926539;
my $theta = atan2($V-128, $U-128) * 180.0/$PI;
my $hue = ($theta > 0) ? $theta : 360 + $theta;
my $sat = sqrt( ($U-128)**2 + ($V-128)**2 );
$H_a = int($hue);
$S_a = int($sat);
$V_a = 0; # max
foreach my $m ($R,$G,$B) { $V_a = $m > $V_a ? $m : $V_a; }
# RGB->HSV from wikipedia
($R1,$G1,$B1) = ($R/255,$G/255,$B/255);
my ($max,$min) = (0,1);
foreach my $m ($R1,$G1,$B1) {
$max = $m > $max ? $m : $max;
$min = $m < $min ? $m : $min;
}
$H1 = 'unknown';
if ($max == $min) { $H1 = 'undefined'; }
elsif ($max == $R1 && $G1 >= $B1) { $H1 = 60 * ($G1 - $B1) / ($max - $min); }
elsif ($max == $R1 && $G1 < $B1) { $H1 = 60 * ($G1 - $B1) / ($max - $min) + 360; }
elsif ($max == $G1) { $H1 = 60 * ($B1 - $R1) / ($max - $min) + 120; }
elsif ($max == $B1) { $H1 = 60 * ($R1 - $G1) / ($max - $min) + 240; }
$S1 = ($max == 0 ? 0 : 1 - $min/$max);
$V1 = $max;
if (!($H1 eq 'undefined')) { $H1 = int($H1); } # round
# RGB->HSL from wikipedia
$H2 = $H1;
$L = ($max + $min) / 2;
if ($L == 0) { $S2 = 0; }
elsif ($L == 1) { $S2 = 1; } # avoid division by zero in final elsif
elsif (0 < $L && $L <= 0.5) { $S2 = ($max - $min) / ($max + $min); }
elsif (0.5 < $L) { $S2 = ($max - $min) / (2 - ($max + $min)); }
# RGB->HSP
$H3 = $H1;
$S3 = $S1; # official C code seems to use H and S from HSV
$P = sqrt(.241 * $R1**2 + .691 * $G1**2 + .068 * $B1**2);
# print
my $V_a_1 = $V_a / 255;
my $V1_255 = int($V1 * 255);
my $L_255 = int($L * 255);
my $P_255 = int($P * 255);
print "Ranges: R,G,B,Y,U,V:[0-255], H:[0-360], S,V:[0-1], S for avisynth:[0-119?]\n";
print "----------------------------------------\n";
print "RGB - R: $R, G: $G, B: $B\n";
print "avisynth YUV (Rec601) - Y: $Y, U: $U, V: $V\n";
print "avisynth HSV - H: $H_a, S: $S_a, V: $V_a ($V_a_1)\n";
print "wikipedia HSV - H: $H1, S: $S1, V: $V1 ($V1_255)\n";
print "wikipedia HSL - H: $H2, S: $S2, V: $L ($L_255)\n";
print "HSP - H: $H3, S: $S3, V: $P ($P_255)\n";
# ----- Functions -----
sub ScaledPixelClip
{
my $i = shift;
return PixelClip(($i+32768) >> 16);
}
sub PixelClip
{
my $i = shift;
return $i > 255 ? 255 : ($i < 0 ? 0 : $i);
}
Example output of the program:
>rgb2hsv.pl
Usage: rgb2hsv R G B
(input range: 0-255)
>rgb2hsv.pl 0 0 255
Ranges: R,G,B,Y,U,V:[0-255], H:[0-360], S,V:[0-1], S for avisynth:[0-119?]
----------------------------------------
RGB - R: 0, G: 0, B: 255
avisynth YUV (Rec601) - Y: 41, U: 240, V: 110
avisynth HSV - H: 350, S: 113, V: 255 (1)
wikipedia HSV - H: 240, S: 1, V: 1 (255)
wikipedia HSL - H: 240, S: 1, V: 0.5 (127)
HSP - H: 240, S: 1, V: 0.260768096208106 (66)
--
Greets,
jmac698
2nd November 2006, 16:35
In computer graphics, hue is a straightforward calculation, but when you are dealing with video there is a slight rotation of hue in order to work better with video and the human visual system. It was found a long time ago, that we can see more shades of one color but not another. In order to save bandwidth, the color axis was rotated. U,V are used with equal bandwidth now (but the old I,Q were different). There is still an axis rotation. Does some combination of 33deg make sense?
This is from memory, I will have to check the rec.601 standards.
I am interested in working on this because I need a formula to find rotation from U,V. Also many plugins have this wrong, like the vectorscope plugin.. EDIT: I know why now, they didn't stretch the axis properly. I think you want to stetch U,V to not fit in a circle.
Also U,V is not H, it's the x,y coordinates within a rotated hexagon. I think the avisynth looks right, 110 has to be converted with atn(v/u) I think.
The funny number 33deg comes from measurements of our visual perception.
Wilbert
2nd November 2006, 21:00
HSV is supposed to be the same, no matter where you convert to HSV from, no?
If you consider the colorspace HSV, then of course this is true. The main issue is whether the hue (as in atn(v/u)) is the same as the hue in HSV. Personally i don't think so, but i have no idea in what sense the two are different (other then the zero degree position, but that's not really important).
IanB
3rd November 2006, 02:54
Avisynth has no true HSV colour space so any interim calculations used by the filters like Tweak are a compromise in favour of performance (and easy code writing).
The main issue in effect here is that the U and V signals are normalized so that they each occupy the [16..240] range of a byte. For speed reasons we don't reverse this normalization, apply the filter and then renormalize. In practicle terms it makes little difference as the saturation parameter can be used in conjunction with hue to get a correct overall result.
The Avisynth view of the HSV colour circle is really an ellipse.
[The 33 degrees bit is to do with NTSC YIQ encoding.]
jmac698
3rd November 2006, 22:13
I was just going to say something like that. The U,V axis are not scaled evenly using the formula in the standards. Therefore atn(v/u) will not be correct, but distorted. The correction is what I've been trying to calculate.
Here we go, v (r-y) is scaled by .5/(1-Kr)=.713 and u (b-y) .5/(1-Kb)=.564, these two numbers being different means the axis are scaled different. To produce an equalized scaled space you need to rotate and rescale.
R
|v Mag
|
Yl ---+----u
| Blu
G |
Cyn
green and cyan should be equal on the V axis but they aren't because of rotation.
The scales are set because cyan and red are the highest and lowest points, which are wider than green and magenta, but they should be equal. It helps to see a good diagram...
jmac698
4th November 2006, 04:38
First we have to know how each program is doing it's computations.
gimp 2.2.13.tar.gz, libgimpcolor/gimpcolorspace.c:
/**
* gimp_rgb_to_hsv:
* @rgb: A color value in the RGB colorspace
* @hsv: The value converted to the HSV colorspace
*
* Does a conversion from RGB to HSV (Hue, Saturation,
* Value) colorspace.
**/
void
gimp_rgb_to_hsv (const GimpRGB *rgb,
GimpHSV *hsv)
{
gdouble max, min, delta;
g_return_if_fail (rgb != NULL);
g_return_if_fail (hsv != NULL);
max = gimp_rgb_max (rgb);
min = gimp_rgb_min (rgb);
hsv->v = max;
delta = max - min;
if (delta > 0.0001)
{
hsv->s = delta / max;
if (rgb->r == max)
{
hsv->h = (rgb->g - rgb->b) / delta;
if (hsv->h < 0.0)
hsv->h += 6.0;
}
else if (rgb->g == max)
{
hsv->h = 2.0 + (rgb->b - rgb->r) / delta;
}
else if (rgb->b == max)
{
hsv->h = 4.0 + (rgb->r - rgb->g) / delta;
}
hsv->h /= 6.0;
}
else
{
hsv->s = 0.0;
hsv->h = 0.0;
}
hsv->a = rgb->a;
}
avisynth_256_src.zip, src\filters\levels.cpp:
/**********************
****** Tweak *****
**********************/
Tweak::Tweak( PClip _child, double _hue, double _sat, double _bright, double _cont, bool _coring, bool _sse,
IScriptEnvironment* env )
: GenericVideoFilter(_child), coring(_coring), sse(_sse)
{
try { // HIDE DAMN SEH COMPILER BUG!!!
if (vi.IsRGB())
env->ThrowError("Tweak: YUV data only (no RGB)");
if (vi.width % 2)
env->ThrowError("Tweak: Width must be a multiple of 2; use Crop");
// The new "mapping" C code is faster than the iSSE code on my 3GHz P4HT - Make it optional
if (sse && (coring || !vi.IsYUY2()))
env->ThrowError("Tweak: SSE option only available for YUY2 with coring=false");
if (sse && !(env->GetCPUFlags() & CPUF_INTEGER_SSE))
env->ThrowError("Tweak: SSE option needs an iSSE capable processor");
Sat = (int) (_sat * 512);
Cont = (int) (_cont * 512);
Bright = (int) _bright;
const double Hue = (_hue * 3.14159265358979323846) / 180.0;
const double SIN = sin(Hue);
const double COS = cos(Hue);
Sin = (int) (SIN * 4096 + 0.5);
Cos = (int) (COS * 4096 + 0.5);
int maxY = coring ? 235 : 255;
int minY = coring ? 16 : 0;
for (int i = 0; i < 256; i++)
{
/* brightness and contrast */
int y = int((i - 16)*_cont + _bright + 16.5);
map[i] = min(max(y,minY),maxY);
/* hue and saturation */
mapCos[i] = int(((i - 128) * COS * _sat + 128) * 256. + 128.5);
mapSin[i] = int( (i - 128) * SIN * _sat * 256. + 0.5);
}
}
catch (...) { throw; }
}
jmac698
4th November 2006, 06:30
Here's a table:
color rgb hsv,deg,%,% yuv,rec.601 angle
red 255,0,0 0,100,100 81,90,240 109
yel 255,255,0 60,100,100 210,16,146 171
grn 0,255,0 120,100,100 145,54,34 232
cyn 0,255,255 180,100,100 170,166,16 289
blu 0,0,255 240,100,100 41,240,110 351
mag 255,0,255 300,100,100 106,202,222 52
red 128,0,0 0,100,50 49,109,184 109
red 255,128,128 0,50,100 158,109,184 109
I think the rgb is rotated +123deg then scaled. So the last step is to put 3 or 4 pieces together to a final matrix to convert hsv to tweak...
Ignus2
4th November 2006, 12:29
Actually, the reason why I was curious about this HSV stuff, is because I wanted to do color selection. First I used MaskHS, and I wanted to make exact parameters for it (hence my confusion about the HSV workings of Tweak/MaskHS). But later we decided, that a non-binary way would be better, in which the mask is linear, according to the current pixel's color distance from the desired color.
I thought HSV would be nice to use, so I implemented a little plugin, but it doesn't give what I wanted. I couldn't come up with a good metric in HSV. Yet, I've found this:
http://www.compuphase.com/cmetric.htm
But that is for RGB. Has anyone been experimenting with this?
What really would be the best, is if I had a metric, that works entirely in YUV (taking all 3 parameters into account). Applying the above metric to RGB values converted from YUV is OK, but I want to skip RGB conversion.
Wilbert
4th November 2006, 15:23
Ok, i think i see the problem. I haven't found out the correct formulas yet, which takes into account that scale problem.
@Ignues2,
Apart from the problem above, I was thinking about the following metric:
color1 (which is given in advance): Y1Cb1Cr1 -> hue1 -> mask=1
color2: Y2Cb2Cr2 such that -> hue2= |180-hue1| -> mask=0
linear interpolation:
hue = (hue1-|180-hue1|)*mask+|180-hue1| = 180*sign(hue1)*mask+|180-hue1|
with sign(hue1) = 1 for hue1>0, = 0 for hue1=0, < 1 for hue1<0
and thus for an arbitrary pixel:
YCbCr -> hue -> mask = (hue - |180-hue1|)/(180*sign(hue1))
Is that the one you tried? If so, what kind of problems does it give?
edit: corrected formulas!
jmac698
4th November 2006, 16:04
I see your problem, and a simple formula may work for you, however it would be ideal to use a perceptually normalized colorspace like L*a*b* or L*u*v*. The theory is that, the colors you are selecting by eye, are using the eye's model of nearby colors, so if you want the program to linearly select the nearby colors, you have to use the eye's model.
So, convert everything to L*u*v*, then use your simple formulas on that colorspace, and you will be ok.
Since it's an absolute reference, converting is not easy, you have to know what numbers to use for the primary (I would say D65), and to really work right your monitor has to be calibrated (you can assume all modern monitors are rec.709 and d65, and also sRGB).
Ignus2
8th November 2006, 10:46
What I have tried is this: take the Hue degree between color1 (given in advance) and color2 (pixels of clip). This ranges between 0-180. Scale this to 0-255 linearly, and that's the difference.
Of course, this needs to be inverted to get an image, where the black pixels represent the biggest distance.
But this somehow doesn't give the right results. Maybe the Hue difference is too little on my video. Scaling with Levels(), the results can be improved though.
@jmac698:
I have been looking for info about L*u*v* and L*a*b* colorspaces, as I get it, they are absolute colorspaces, and conversion from RGB is not straightforward. I'll have to look into this a bit more, as I only read a few pages about it.
Wilbert
8th November 2006, 12:02
What I have tried is this: take the Hue degree between color1 (given in advance) and color2 (pixels of clip). This ranges between 0-180. Scale this to 0-255 linearly, and that's the difference.
Ok, that's the same approach as in my post above.
But this somehow doesn't give the right results. Maybe the Hue difference is too little on my video. Scaling with Levels(), the results can be improved though.
Could you post some screenshots of your results and a description of what you want it to look like?
Wilbert
14th November 2006, 22:10
@Ignus2,
I'm still waiting for a reply of you :)
Ignus2
8th May 2007, 00:50
Sorry for the long delay, but it's a school project we're working on, and we had other matters to attend to.
The solution I have done, was that I wrote a plugin (named ColorDiff), which makes an Y8 frame according to the input frame's pixels' "distance" from a specified color. The diff is calculated by simply getting the diff of the individual color components from the desired color's components and summing them up. Also, weights can be set for the individual diffs. It can work in YUV, RGB and HSL (wikipedia) mode.
The resulting Y8 frame is greyscale.
If anyone needs this stuff, just post, and I'll pack it up. It's nothing fancy, really, and I'm no expert on this color stuff. It just does exactly what we needed here.
It's a kind of MaskHS() in a non-binary (greyscale) way.
--
Greets,
I.
Wilbert
8th May 2007, 22:57
1) Could you posts some screenshots showing the effect of this filter?
2) For what color format did you implement it?
Ignus2
9th May 2007, 04:09
I've attached a zip containing the plugin and an example script.
Documentation is at the beginning of the source file.
EDIT: Attachment removed, new version uploaded in a newer post.
--
Greets,
I.
Wilbert
22nd May 2007, 22:50
I'm using latest v2.58, but your filter doesn't work for me:
LoadCPlugin("F:\Plugins3\Colordiff\colordiff\colordiff.dll")
ImageSource("F:\Plugins3\Colordiff\colordiff\linuXP.jpg").ConvertToRGB32()
ColorDiff(c1=100, c2=100, c3=100, w1=2.0, w2=2.0, w3=2.0, colorspace="RGB")
LoadCPlugin("F:\Plugins3\Colordiff\colordiff\colordiff.dll")
ImageSource("F:\Plugins3\Colordiff\colordiff\linuXP.jpg").ConvertToYUY2()
ColorDiff(c1=100, c2=100, c3=100, w1=2.0, w2=2.0, w3=2.0, colorspace="YUV").ConvertToYUY2()
In both cases I get no image, and the following error message: Filter attempted to create VideoFrame with invalid pixel_type.
Btw, what do the cj and wj parameters do?
Ignus2
24th May 2007, 14:32
Yep, it creates Y8 frames, and that's only available in 2.6
About the parameters, look in the source. It's explained at the top of the file in comments.
--
Greets,
I.
@Ignus2,
And why doesn't you plugin test that it has a Y8 capable host and respond appropriatly in the constructor. It's really bad form to wait until the first frame is rendered to fall apart at the seams.
You chose to violate the API, its your job to validate your extensions.
Ignus2
26th May 2007, 21:24
I don't care who or what I violate, there's the source, go ahead and fix it, if it's annoying you that much.
If you have questions about using the plugin, I'll gladly answer them.
Just don't complain please.
--
Greets,
I.
Ignus2
26th November 2007, 21:51
Made some minor fixes.
I removed the earlier attachment, here is the new one.
--
Greets,
I.
Mug Funky
28th November 2007, 02:47
just to throw a spanner in, generally Red is at 122 degrees. ie, on a 601 vectorscope it will sit there, and on most colour correctors this is the angle you push the joyballs to go toward red.
Ignus2
29th November 2007, 01:06
What do you mean by "generally"?
As you can see, I have found as many variations of this, as there are places it is mentioned at :)
vBulletin® v3.8.5, Copyright ©2000-2012, Jelsoft Enterprises Ltd.