6

I read in colored jpg images using readJPEG() from the jpeg package. Now I have my images as three-dimensional arrays (width, height, channels) in R.

I want to convert these image arrays into the HSL or HSV color space, mutate the images and save them as JPGs in the RGB format again. However, as the images are quite large (5000 x 8000), it would be too time consuming to loop through every single cell. I found the package OpenImageRto convert the image to the HSV color space quickly, however, I am confused by large negative values in the "saturation" channel. Also, the package contains no functions to convert the image back.

Is there any package to perform fast conversions from RGB to HSL or HSV (and back)? Or is there any other way to perform the converison quickly?

These are my current attempts for converting into one direction, element-wise:

# load packages
library(jpeg)
library(plotwidgets)    

# load image
img <- readJPEG(img_path)
img <- img * 255
  
# new empty image
img_new <- array(NA, dim = dim(img))

# this takes way too long
for (img_row in 1:dim(img)[1]) {
  for (img_col in 1:dim(img)[2]) {
    img_new[img_row,img_col,] <- round(rgb2hsl(as.matrix(img[img_row,img_col,])))
  }
}

# this takes also way too long
for (img_row in 1:dim(img)[1]) {
  img_new[img_row,,] <- t(round(rgb2hsl(t(matrix(img[img_row,,], ncol = 3)))))
}

# this takes also ages
rgb_hsl_fun <- function(x) {
  as.numeric(rgb2hsl(matrix(x)))
}
img_hsl <- apply(X = img, MARGIN = c(1,2), FUN = rgb_hsl_fun)
Zoe
  • 906
  • 4
  • 15

2 Answers2

3

The whole thing is quite simple to do. Use the colorspace library for this.

Here is my original img.jpg file. enter image description here

Here is the code.

library(jpeg)
library(colorspace)

#Reading a jpg file
img = readJPEG("img.jpg") * 255

#Row-by-row conversion
for(i in 1:dim(img)[1]){
  
  #Convert to HSV format
  hsv = RGB(img[i,,1], img[i,,2], img[i,,3]) |> as("HSV")
  
  #Mutation of H, S, V components
  attributes(hsv)$coords[,"H"] = attributes(hsv)$coords[,"H"]/2 
  attributes(hsv)$coords[,"S"] = attributes(hsv)$coords[,"S"]*.998 
  attributes(hsv)$coords[,"V"] = attributes(hsv)$coords[,"V"]-1 
  
  #Convert to RGB format and save to the current line.
  rgb = as(hsv, "RGB")
  img[i,,1] = attributes(rgb)$coords[,"R"]
  img[i,,2] = attributes(rgb)$coords[,"G"]
  img[i,,3] = attributes(rgb)$coords[,"B"]
}

#Save to JPG file
writeJPEG(img / 255, "img_hsv.jpg")

Just note that to get to the individual H, S, V (or R, G, B) components you have to use the coords attribute.

As you can see, my mutation of the components H, S, V was as follows:

  • H = H / 2
  • S = S * 0.998
  • V = V-1

After this mutation, the original file looks like this. enter image description here

However, if you prefer to carry out the mutation on the HLS palette, it is possible.

#Reading a jpg file
img = readJPEG("img.jpg") * 255

#Row-by-row conversion
for(i in 1:dim(img)[1]){
  
  #Convert to HLS format
  hls = RGB(img[i,,1], img[i,,2], img[i,,3]) |> as("HLS")
  
  #Mutation of H, S, V components
  attributes(hls)$coords[,"H"] = attributes(hls)$coords[,"H"]/2 
  attributes(hls)$coords[,"L"] = attributes(hls)$coords[,"L"]/2 
  attributes(hls)$coords[,"S"] = attributes(hls)$coords[,"S"]/2 
  
  #Convert to RGB format and save to the current line.
  rgb = as(hls, "RGB")
  img[i,,1] = attributes(rgb)$coords[,"R"]
  img[i,,2] = attributes(rgb)$coords[,"G"]
  img[i,,3] = attributes(rgb)$coords[,"B"]
}

#Save to JPG file
writeJPEG(img / 255, "img_hls.jpg")

Here is the image with H/2, L/2 and S/2 conversion. enter image description here

Hope this is what you were looking for.

Marek Fiołka
  • 4,825
  • 1
  • 5
  • 20
0

It would be wise to open an issue to the Github repository (in case that there is a quick fix to the error case for the HSV transformation). For the record I'm the author and maintainer of the OpenImageR package.

I took a look once again to the code of the RGB_to_HSV function and as I mention at the top of the function of the Rcpp code the implementation is based on the paper

Analytical Study of Colour Spaces for Plant Pixel Detection, Pankaj Kumar and Stanley J. Miklavcic, 2018, Journal of Imaging (page 3 of 12) or section 2.1.3,

The negative values of the saturation channel were highly probable related to a mistake of the following line,

 S(i) = 1.0 - (3.0 * s_val) * (R(i) + G(i) + B(i));

which actually (based on the paper) should have been:

 S(i) = 1.0 - (3.0 * s_val) / (R(i) + G(i) + B(i));

(division rather than multiplication of the last term)

I uploaded the updated version to Github and you can install it using

remotes::install_github('mlampros/OpenImageR')

and please report back if it works so that I can upload the new version to CRAN.

The package does not include a transformation from HSV to RGB (from what I understand you want to modify the pixel values and then convert to RGB).

lampros
  • 581
  • 5
  • 12
  • 1
    Sorry, that I did not open an issue. I just thought I might have misunderstood what the output of the function is supposed to be! – Zoe Dec 09 '21 at 12:53
  • I used the image used in the other answer. When I load it in, the values range in each RGB channel from 0 to 255. When using your function (I reinstalled from your github), I get both weird value ranges (H 0 - 6.278; S 0 - 1; L 0 -255) and many NA values in the S band. – Zoe Dec 09 '21 at 13:28
  • the only thing I can suggest is to open an issue by adding a reproducible example to find out if it has to do with the RGB_to_HSV() function. – lampros Dec 09 '21 at 19:13