changeset 11687:2c39ee206f66 octave-forge

rgb2ycbcr: complete rewrite * calculations are now made with double precision with a color matrix * can now accept images of class double, single, uint8 and uint16 * can now accept colormaps * added tests * new argument to specify standard * new argument to specify Kb and Kr
author carandraug
date Sun, 05 May 2013 07:48:19 +0000
parents 9f74cc14b4e6
children 101a3bd532ed
files main/image/NEWS main/image/inst/rgb2ycbcr.m
diffstat 2 files changed, 116 insertions(+), 20 deletions(-) [+]
line wrap: on
line diff
--- a/main/image/NEWS	Sun May 05 04:09:40 2013 +0000
+++ b/main/image/NEWS	Sun May 05 07:48:19 2013 +0000
@@ -30,6 +30,10 @@
 
       imrotate_Fourier
 
+ ** rgb2ycbcr was completely rewritten to accept images of other classes, and
+    colormaps.  A new argument was implemented to convert the RGB values
+    according to different standards.
+
  ** The use of non logical matrices to specify the neighborhood for the medfilt2
     function has been deprecated.  If using a vector to specify the size of the
     neighborhood, the elements are no longer swapped.
--- a/main/image/inst/rgb2ycbcr.m	Sun May 05 04:09:40 2013 +0000
+++ b/main/image/inst/rgb2ycbcr.m	Sun May 05 07:48:19 2013 +0000
@@ -1,4 +1,4 @@
-## Copyright (C) 2011 Santiago Reyes González <mailalcuete@gmail.com>
+## Copyright (C) 2013 Carnë Draug <carandraug@octave.org>
 ##
 ## This program is free software; you can redistribute it and/or modify it under
 ## the terms of the GNU General Public License as published by the Free Software
@@ -14,29 +14,121 @@
 ## this program; if not, see <http://www.gnu.org/licenses/>.
 
 ## -*- texinfo -*-
-## @deftypefn {Function File} @var{B} = rgb2ycbcr (@var{A})
-## Perform colour convertion from RGB to YCbCr on a given image.
+## @deftypefn  {Function File} {@var{YCbCrmap} =} rgb2ycbcr (@var{cmap})
+## @deftypefnx {Function File} {@var{YCbCr} =} rgb2ycbcr (@var{RGB})
+## @deftypefnx {Function File} {@dots{} =} rgb2ycbcr (@dots{}, [@var{Kb} @var{Kr}])
+## @deftypefnx {Function File} {@dots{} =} rgb2ycbcr (@dots{}, @var{standard})
+## Convert RGB values to YCbCr.
+##
+## The convertion changes the image @var{RGB} or colormap @var{cmap}, from
+## the RGB color model to YCbCr (luminance, chrominance blue, and chrominance
+## red).  @var{RGB} must be of class double, single, uint8, or uint16.
 ##
-## The image @var{A} must be a NxMx3 image. The conversion
-## The convertion changes the image from the RGB color model to YCbCr e.g.
-## @example
-## imOut = rgb2ycbcr(imIn);
-## @end example
-## Currently this function only works with @samp{uint8} and will always return
-## an @samp{uint8} matrix.
-## @seealso{cmunique}
+## The formula used for the conversion is dependent on two constants, @var{Kb}
+## and @var{Kr} which can be specified individually, or according to existing
+## standards:
+##
+## @table @asis
+## @item "601" (default)
+## According to the ITU-R BT.601 (formerly CCIR 601) standard.  Its values
+## of @var{Kb} and @var{Kr} are 0.114 and 0.299 respectively.
+## @item "709" (default)
+## According to the ITU-R BT.709 standard.  Its values of @var{Kb} and
+## @var{Kr} are 0.0722 and 0.2116 respectively.
+## @end table
+##
+## @seealso{hsv2rgb, ntsc2rgb, rgb2hsv, rgb2ntsc}
 ## @end deftypefn
 
-function im_out =  rgb2ycbcr(im)
-  if (nargin != 1)
-    print_usage;
-  elseif (length(size(im)) != 3 || size(im,3) != 3)
-    error("image must be NxMx3");
+function ycbcr = rgb2ycbcr (rgb, standard = "601")
+
+  img = false; # was input an image?
+
+  if (nargin < 1 || nargin > 2)
+    print_usage ();
+  endif
+
+  if (iscolormap (rgb))
+    ## do nothing, it's a colormap
+  elseif (isrgb (rgb))
+    img = true;
+    ## if we have it in 2D, we can use matrix multiplcation
+    nRows = rows (rgb);
+    nCols = columns (rgb);
+    rgb   = reshape (rgb, [nRows*nCols 3]);
+  else
+    error ("rgb2ycbcr: input must be a colormap (Nx3) or RGB image (NxMx3)");
+  endif
+
+  ## TODO would be interesting to accept arbitrary values of Kb and Kr
+  if (! ischar (standard))
+    error ("rgb2ycbcr; STANDARD must be a string `601' or `709'");
+  elseif (strcmpi (standard, "601"))
+    ## these are the values for ITU-R BT.601
+    Kb = 0.114;
+    Kr = 0.299;
+  elseif (strcmpi (standard, "709"))
+    ## these are the values for ITU-R BT.709
+    Kb = 0.0722;
+    Kr = 0.2126;
+  else
+    error ("rgb2ycbcr: unknown standard `%s'", standard);
   endif
 
-  im            = im2double(im);
-  im_out(:,:,1) = uint8(floor(77*im(:,:,1)    + 150*im(:,:,2) + 29*im(:,:,3)));
-  im_out(:,:,2) = uint8(floor(((-44*im(:,:,1) - 87*im(:,:,2)  + 131*im(:,:,3))/256 + 0.5)*256));
-  im_out(:,:,3) = uint8(floor(((131*im(:,:,1) - 110*im(:,:,2) - 21*im(:,:,3))/256 + 0.5)*256));
+  ## the color matrix for the conversion. Derived from:
+  ##    Y  = Kr*R + (1-Kr-Kb)*G + kb*B
+  ##    Cb = (1/2) * ((B-Y)/(1-Kb))
+  ##    Cr = (1/2) * ((R-Y)/(1-Kr))
+  ## It expects RGB values in the range [0 1], and returns Y in the
+  ## range [0 1], and Cb and Cr in the range [-0.5 0.5]
+  cmat = [  Kr            (1-Kr-Kb)            Kb
+          -(Kr/(2-2*Kb)) -(1-Kr-Kb)/(2-2*Kb)   0.5
+            0.5          -(1-Kr-Kb)/(2-2*Kr) -(Kb/(2-2*Kr)) ];
+
+  cls   = class (rgb);
+  rgb   = im2double (rgb);
+  ycbcr = (cmat * rgb')'; # transpose in the end to get back colormap shape
+  ## rescale Cb and Cr to range [0 1]
+  ycbcr(:, [2 3]) += 0.5;
+  ## footroom and headroom will take from the range 16/255 each for Cb and Cr,
+  ## and 16/255 and 20/255 for Y. So we have to compress the values of the
+  ## space, and then shift forward
+  ycbcr(:,1) = (ycbcr(:,1) * 219/255) + 16/255;
+  ycbcr(:,[2 3]) = (ycbcr(:,[2 3]) * 223/255) + 16/255;
 
+  switch (cls)
+    case {"single", "double"}
+      ## do nothing. All is good
+    case "uint8"
+      ycbcr = im2uint8 (ycbcr);
+    case "uint16"
+      ycbcr = im2uint16 (ycbcr);
+    otherwise
+      error ("rgb2ycbcr: unsupported image class %s", cls);
+  endswitch
+
+  if (img)
+    ## put the image back together
+    ycbcr = reshape (ycbcr, [nRows nCols 3]);
+  endif
 endfunction
+
+%!test
+%! in(:,:,1) = magic (5);
+%! in(:,:,2) = magic (5);
+%! in(:,:,3) = magic (5);
+%! out(:,:,1) = [31  37  17  23  29
+%!               36  20  22  28  30
+%!               19  21  27  33  35
+%!               25  26  32  34  19
+%!               25  31  37  18  24];
+%! out(:,:,2) = 128;
+%! out(:,:,3) = 128;
+%! assert (rgb2ycbcr (uint8 (in)), uint8 (out));
+
+%!test
+%! out(1:10, 1)  = linspace (16/255, 235/255, 10);
+%! out(:, [2 3]) = 0.5;
+%! assert (rgb2ycbcr (gray (10)), out, 0.00001);
+
+%!assert (rgb2ycbcr ([1 1 1]), [0.92157 0.5 0.5], 0.0001);