I looked at ColorConvert
, but haven't found a relevant option. Is there a built-in function do this?
4 Answers
This is using the digital formula given at Wikipedia. ImageAdjust
was then used to scale computed YCbCR levels from 16..235
back to 0..1
for final display.
ClearAll[rgb2YCbCr]
rgb2YCbCr[{r_, g_, b_}] :=(*ref:http://en.wikipedia.org/wiki/YCbCr, digital formula*)
{16 + (65.481 r + 128.553 g + 24.966 b),
128 + (-37.797 r - 74.203 g + 112 b),
128 + (112 r - 93.786 g - 18.214 b)
};
lena = Import["ExampleData/lena.tif"];
lenaYCbCr = Map[rgb2YCbCr, ImageData[lena], {2}];
{red, blue, green} = MapThread[ImageMultiply, {ColorSeparate[lena],
{Red, Green,Blue}}]; (*thanks to chat room for this trick*)
c1 = lenaYCbCr; c1[[All, All, {2, 3}]] = 1;
c2 = lenaYCbCr; c2[[All, All, {1, 3}]] = 1;
c3 = lenaYCbCr; c3[[All, All, {1, 2}]] = 1;
n = 300;
Grid[{
{"RGB", "YCbCr", SpanFromLeft}, {Image[lena, ImageSize -> 350],
ImageAdjust[Image[lenaYCbCr, ImageSize -> 350]], SpanFromLeft},
{Image[red, ImageSize -> n], "channel 1", ImageAdjust@Image[c1, ImageSize -> n]},
{Image[green, ImageSize -> n], "channel 2", ImageAdjust@Image[c2, ImageSize -> n]},
{Image[blue, ImageSize -> n], "channel 3", ImageAdjust@Image[c3, ImageSize -> n]}
}, Alignment -> Center, Frame -> True,
FrameStyle -> Directive[Thickness[.5], LightGray]
]
This below shows the 3 channels of each image
(for faster implementation of rgb2YCbCr[]
please see MrWizard's code on this page)
-
$\begingroup$ Just as a side note,
lena2=Map[rgb2YCbCr, ImageData[lena], {2}]
should suffice instead of all those temporary variables. $\endgroup$– C. E.Aug 4, 2013 at 9:35
Despite all the efforts of Nasser and MrWizard to make this fast, I believe the most natural way to do this is to use ImageApply
because it is fast, it rescales the data for you and you don't have to extract the ImageData
by yourself. So taking the transformation matrix directly from Wikipedia for normalised RGB values and using their sample image too you get
img = Import[
"http://upload.wikimedia.org/wikipedia/commons/d/d0/Barns_grand_tetons.jpg"];
m = {{0.299, .587, .114}, {-.168736, -.331264, .5}, {.5, -.418688, -.081312}};
the conversion is simply
ImageApply[m.# &, img]
This is almost twice as fast as the solution of MrWizard on my machine here (0.640s compared to 0.356s), but note that this was only tested on Mathematica 8 and 9 on Linux and MacOSX.
Furthermore, be aware that the two colour channels are in the interval [-0.5, 0.5]
so using ColorSeparate
will produce very dark images in the last two channels.
If you want to create the visualisation of the different channels, which is shown on the right side on the Wikipedia article page, you could simply use the inverse matrix. I shifted the colour channels by 0.5 here for display purposes
mi = Inverse[m];
ImageApply[#1, #2] & @@@
Transpose[{{mi.{#, 0, 0} &, mi.{.5, # - .5, 0} &, mi.{.5, 0, # - .5} &},
ColorSeparate[ImageApply[m.# + {0, .5, .5} &, img]]}]
Timings
Regarding Mr.Wizards comment
This is five times slower than my method on my system (v7). Before I reconsider my vote can you explain why this would be faster than Dot in later versions?
I compared the following timings,
ImageApply[m.#+{0.0,0.5,0.5}&, img] // Timing // First
RGBtoYCbCr[img] // Timing // First
where first line is the method I described with the shift of {0,1/2,1/2}
included to make it more comparable to MrWizards method. The second line is the function from Mr.Wizards code.
Running this code 100 times to mean out cosmic radiation, I get the following mean timings
- Linux, Mathematica 9.0.1 halirutan: 0.305819 Mr.Wizard: 0.381984
- Linux, M8.0.4 halirutan: halirutan: 0.354542 Mr.Wizard: 0.394065
I believe the difference in timing is purely the overhead of pattern matching and ImageData
call.
-
$\begingroup$ That vote-unvote was mine. This is five times slower than my method on my system (v7). Before I reconsider my vote can you explain why this would be faster than
Dot
in later versions? $\endgroup$ Aug 5, 2013 at 4:47 -
$\begingroup$ I couldn't figure out how to recreate that visualization yesterday so this made me glad. :) (btw
mi = Inverse[m];
is omitted). +1 $\endgroup$– C. E.Aug 5, 2013 at 7:36 -
$\begingroup$ @Anon Thanks, I fixed it. I haven't tried the code of my answer yesterday. Usually I do this and check whether everything works on a fresh kernel. $\endgroup$– halirutan ♦Aug 5, 2013 at 7:38
-
$\begingroup$ @Mr.Wizard I think I don't understand you last sentence in your comment. I use
Dot
too and I think the different timing has nothing to do with it. The real purpose of my answer was to show that you can do the conversion directly on the image withImageApply
and you are still very fast. (Btw, I have no version 7 installed where I could test it) $\endgroup$– halirutan ♦Aug 5, 2013 at 7:41 -
$\begingroup$ Pardon, I meant using
Dot
on the entire array at once versus usingImageApply
to (seemingly) applyDot
to each triplet individually. I wonder if there is some special compilation taking place in v8+ forImageApply
. As I said it's slower in v7. By the way ism.# &+{0.0,0.5,0.5}
merely nonfunctional notation or is that also doing something special? $\endgroup$ Aug 5, 2013 at 8:27
Here is an array-optimized version of Nasser's formula. It is up to two orders of magnitude faster.
It handles data with an array depth of 1, 2, or 3, or an Image
object.
Timing comparision:
horse = Import["https://i.stack.imgur.com/ZhYwB.jpg"];
Map[rgb2YCbCr, ImageData[horse], {2}] // Timing // First
RGBtoYCbCr[horse] // Timing // First
6.303
0.081
Sample output:
ImageAdjust @ Image[RGBtoYCbCr @ horse, ImageSize -> 500]
Code:
With[{
v1 = Developer`ToPackedArray @ {16`, 128`, 128`},
m1 = Developer`ToPackedArray @
{{65.481, 128.553, 24.966}, {-37.797, -74.203, 112`}, {112`, -93.786, -18.214}}
},
RGBtoYCbCr[a : {_, _, _}?VectorQ] := v1 + m1.a;
RGBtoYCbCr[img_Image] := RGBtoYCbCr @ ImageData[img, "Byte"];
RGBtoYCbCr[a_ /; MatchQ[Dimensions@a, {_, 3}]] := Transpose[v1 + m1.Transpose[a]];
RGBtoYCbCr[a_ /; MatchQ[Dimensions@a, {_, _, 3}]] :=
Transpose[v1 + m1.Transpose[a, {2, 3, 1}], {3, 1, 2}];
]
A compiled version of Nasser's formula,
ClearAll[cRGB2YCbCr]
cRGB2YCbCr= Compile[{{rgb, _Real, 1}},
{16 + (65.481 r + 128.553 g + 24.966 b),
128 + (-37.797 r - 74.203 g + 112 b),
128 + (112 r - 93.786 g - 18.214 b)} /.
Quiet@Thread[{r, g, b} -> Array[rgb[[#]] &, 3]] // Evaluate,
RuntimeAttributes -> Listable
];
horse = Import["https://i.stack.imgur.com/ZhYwB.jpg"];
cRGB2YCbCr@ImageData[horse];//AbsoluteTiming