R: Convert/Read 3D Matrix into a 'magick' object and vice versa - r

I want to work with the magick package for its fantastic image manipulations capabilities. Looking through here I can't seem to find out how to convert a 3D matrix (width x height x channels) to a magick object I can further manipulate, and vice versa.
There is no as.magick function
The as.matrix function does not work
But I would like something like:
height <- 100
width <- 80
X <- array(runif(height * width * 3, min = 0, max = 255), c(height, width, 3))
magick::as.magick(X) %>% magick::image_scale("500x400")
(Obviously I could write the matrix to disk as an image, then read it with magick::image_read, that would be an overkill)
What did I miss?

You can use image_read() to read a matrix as well. However note that the convention is to scale the values between 0 and 1 in case of doubles. So you need to divide your X by 255. Try this:
img <- magick::image_read(X / 255) %>% magick::image_scale("500x400")
If you want to convert the magick object back to an array:
image_data(img, 'rgba')
Or just img[[1]] works as well.

Related

Converting image array to RGB to HSL/HSV and back?

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)
The whole thing is quite simple to do.
Use the colorspace library for this.
Here is my original img.jpg file.
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.
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.
Hope this is what you were looking for.
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).

Quantize grayscale images

I have grayscale images which I want to quantize to different gray levels.
To be more precise, in the EBImage package, we have a function equalize() which has an argument levels. we can set levels value to 256 or 128 or 64 etc to quantize our grayscale images. (But the equalize() function will perform a histogram equalization of the given grayscale image, which is not preferred for my current situation)
Can somebody suggest a formula or a function which we can use to change the number of gray levels in the given grayscale image.
First convert the format in something continous.
Now pseudocode.
int x = (int) (value / (Quantisation));
(new format) y = x * Quantisation;
It is also possible to compress images that lousy way.
The default image data representation in EBImage is a continuous range between 0 and 1. In order to quantize an image to a given number of levels, first convert it to integers in the range of 0:(levels-1), and then back to 0:1, as in the quantize function from the following example.
library(EBImage)
## sample grayscale image
x = readImage(system.file("images", "sample.png", package="EBImage"))
## function for performing image quantization
quantize = function(img, levels) round(img * (levels-1)) / (levels-1)
## quantize the image
y = quantize(x, levels = 8)
## show the result
display(y)

How to get a pixel matrix from grayscale image in R?

When grayscale images are represented by matrices each element of the matrix determines the intensity of the corresponding pixel. For convenience, most of the current digital files use integer numbers between 0 (to indicate black, the color of minimal intensity) and 255 (to indicate white, maximum intensity), giving a total of 256 = 2^8 different levels of gray.
Is there a way to get a pixel matrix of graysale images in R whose pixel values will range from 0 to 255?
It will also be helpful to know if I can resize the images in preferred dimension (say, $28 \times 28$) in R and then convert them into a pixel matrix whose elements range from 0 to 255?
What happens if the original image is RGB but I want the matrix for grayscale?
The R package png offers the readPNG() function which can read raster graphics (consisting of "pixel matrices") in PNG format into R. It returns either a single matrix with gray values in [0, 1] or three matrices with the RGB values in [0, 1].
For transforming between [0, 1] and {0, ..., 255} simply multiply or divide with 255 and round, if desired.
For transforming between RGB and grayscale you can use for example the desaturate() function from the colorspace package.
As an example, let's download the image you suggested:
download.file("http://www.greenmountaindiapers.com/skin/common_files/modules/Socialize/images/twitter.png",
destfile = "twitter.png")
Then we load the packages mentioned above:
library("png")
library("colorspace")
First, we read the PNG image into an array x with dimension 28 x 28 x 4. Thus, the image has 28 x 28 pixels and four channels: red, green, blue and alpha (for semi-transparency).
x <- readPNG("twitter.png")
dim(x)
## [1] 28 28 4
Now we can transform this into various other formats: y is a vector of hex character strings, specifying colors in R. yg is the corresponding desaturated color (again as hex character) with grayscale only. yn is the numeric amount of gray. All three objects are arranged into 28 x 28 matrices at the end
y <- rgb(x[,,1], x[,,2], x[,,3], alpha = x[,,4])
yg <- desaturate(y)
yn <- col2rgb(yg)[1, ]/255
dim(y) <- dim(yg) <- dim(yn) <- dim(x)[1:2]
I hope that at least one of these versions is what you are looking for. To check the pixel matrices I have written a small convenience function for visualization:
pixmatplot <- function (x, ...) {
d <- dim(x)
xcoord <- t(expand.grid(1:d[1], 1:d[2]))
xcoord <- t(xcoord/d)
par(mar = rep(1, 4))
plot(0, 0, type = "n", xlab = "", ylab = "", axes = FALSE,
xlim = c(0, 1), ylim = c(0, 1), ...)
rect(xcoord[, 2L] - 1/d[2L], 1 - (xcoord[, 1L] - 1/d[1L]),
xcoord[, 2L], 1 - xcoord[, 1L], col = x, border = "transparent")
}
For illustration let's look at:
pixmatplot(y)
pixmatplot(yg)
If you have a larger image and want to bring it to 28 x 28, I would average the gray values from the corresponding rows/columns and insert the results into a matrix of the desired dimension.
Final note: While it is certainly possible to do all this in R, it might be more convenient to use an image manipulation software instead. Depending on what you aim at, it might be easier to just use ImageMagick's mogrify for example:
mogrify -resize 28 -type grayscale twitter.png
Here is an example of converting and drawing an image from a grayscale png. Please ensure installing the relevant packages first.
library(png)
library(RCurl)
myurl = "https://postgis.net/docs/manual-dev/images/apple_st_grayscale.png"
my_image = readPNG(getURLContent(myurl))
img_mat=my_image[,,1] # will hold the grayscale values divided by 255
img_mat=t(apply(img_mat, 2, rev)) # otherwise the image will be rotated
image(img_mat, col = gray((0:255)/255)) # plot in grayscale

r image function change size

I'm trying to visualize a matrix using image function. I'd like to set the size of cells (I mean the small squares each of which represents one elements of the matrix). I don't know how many elements will my matrix have beforehand.
This is my code now:
A <- matrix(1:20, 5, 4)
image(A)
and I'd like to have something like this:
image(A, sizeOfCell=10)
Would anyone have some idea?
grid units are probably easiest to work with,
A <- matrix(1:20, 5, 4)
library(grid)
m = A/max(A) # replace with matrix of colours, this will default to grey
grid.raster(m,
width = unit(NROW(A)*5,"mm"), # cell 5mm wide
height = unit(NCOL(A)*4,"mm"),# and 4mm high
interpolate = FALSE)

Grey to Binary Image using R

Is there any R function to convert grey scale image to binary image. There is one to convert from RGB to Grey but I want to convert Grey to Binary.
This is called thresholding or binarization. The most robust in my experience is adaptive thresholding. This is implemented in EBImage as the thresh method
x = readImage(system.file('images', 'nuclei.tif', package='EBImage'))
if (interactive()) display(x)
y = thresh(x, 10, 10, 0.05)
if (interactive()) display(y)
You didn't say what class or "typeof" your data is, so I'm going to provide an answer in a simple case. Suppose your image is an array of integers. These integers range from 0 to, say 512 for a 9-bit greyscale image. You need to decide what the cutoff point is for 0 vs. 1 in your binary image. Then
bin_image <- round(grey_image/max(grey_image),0)
should do it. If your data range from 0 to 1, do a similar operation but adjust the rounding parameters.
Edit: ooops, I left out a choice of cutoff level. Replace max(grey_image) with K*max(grey_image) where K = 1 for cutting at half-max, K>1 to cut higher and K<1 to cut lower.
The EBImage Bioconductor package is a handy tool for performing image analysis in R.
A basic example taken from the package's Vignette:
lena = readImage(system.file("images", "lena.gif", package="EBImage"))
display(lena>0.5)

Resources