Imagemagick: How does RGB work with this software? - imagemagick

I want to change all colors #FF00FF in an image to #0000FF while keeping shades, ideally. So I figured I should at least get it to change colors to begin with to see if the software is even capable of doing things like that.
However its only changing a bit of the color to white and only with a high Fuzz. So it's obvious that RGB in Imagemagick doesn't work like it does anywhere else and I can't find anything to explain how it works.
It seems to replace some off-white with pure white.
Using PHP I do:
exec("convert ".$dir."".$file." -channel RGB -fuzz 30% -opaque rgb\(255,0,255\) -fill rgb\(0,0,255\) ".$dir."".$file);

I am not 100% certain what you mean as you haven't provided a sample of what other software does, but I'll have a try and see if we can get there.
So, if we make a starting image, including your presumed shades of magenta on the left and some test colours on the right:
convert -size 256x256 gradient:black-magenta -size 50x256 \
xc:black xc:white xc:red xc:lime xc:blue +append start.png
And, you want to change magenta shades into blue. I would call that a hue modulation, so I would want to find out the hue angle between blue and magenta, so I would create a 2x1 image with one magenta and one blue pixel and get their HSI values:
convert xc:magenta xc:blue -append -colorspace hsi txt:
Output
# ImageMagick pixel enumeration: 1,2,65535,hsi
0,0: (54612.5,65535,43690) #D555FFFFAAAA hsi(300,100%,66.6667%)
0,1: (43690,65535,21845) #AAAAFFFF5555 hsi(240,100%,33.3333%)
And I can see their hues are 60 degrees apart (300-240). So I would use the -modulate operator, which takes a Brightness, Saturation and Hue, leave the first two unchanged at 100%, and modify the Hue by 60 degrees:
convert start.png -modulate 100,100,60 result.png
Or maybe that is not what you mean? Maybe you only mean to affect specific colour. If so, it gets harder... but not that hard :-)
First, extract the Hue, Saturation and Brightness layers to separate files:
convert start.png -colorspace HSL -separate -colorspace gray HSL-%d.png
That will give us the Hue as a single channel greyscale image in HSL-0.png, the Saturation in HSL-1.png and the Lightness in HSL-2.png.
Now we want to make a new LUT (Lookup Table) for the Hue channel, so we make a 360 pixel long LUT that maps 1:1, i.e. everything maps to normal.
convert -size 1x360 gradient: -rotate 90 greyscale.png
Then we want to dink with the lookups around magenta (300) and make them blue (240). So we want to subtract 60 degrees (which is 0.16 if you scale 0-360 degrees onto the range 0-1) from all pixels in the range 280-320 so there is some tolerance:
convert -size 1x360 gradient: -rotate 90 -colorspace gray -fx "i<280||i>320?u:u-0.16" hueCLUT.png
Now apply that LUT to the Hue of the original image and rebuild it...
convert HSL-0.png -colorspace gray hueCLUT.png -clut HSL-1.png HSL-2.png -set colorspace HSL -combine -colorspace RGB result.png
So, as a simpler script, that might become:
#!/bin/bash
# Make a hue CLUT, transforming magenta hues to blue
convert -size 1x360 gradient: -rotate 90 -colorspace gray -fx "i<295||i>305?u:u-0.16" -resize 256x1! hueclut.png
# Apply to the hue channel
convert start.png -colorspace HSL -write MPR:HSL \
-channel R -separate hueclut.png -clut \
\( MPR:HSL -channel G -separate \) \
\( MPR:HSL -channel B -separate \) \
-set colorspace HSL -combine -colorspace RGB result.png

Related

imagemagick check if image is of almost one single color

Friends,
I have a stack of color-scanned images. Some are from regular white paper with text or images, others were scanned from colored paper (blank pages, same green colored paper used.)
I'd like to identify these colored paper images. Problems:
paper's color ("background") is not scanned very uniformly, often has a wavy or structured pattern
green tone is quite different depending on the scanner used
scanner does not catch the full sheet resulting in a white or shadowed "border" around green area
My idea was to see if say 90% of the image is some sort of green and tried using a sorted histogram. But because of (1) and esp. (2) I have a hard time picking a working color value from the histogram data.
Any help appreciated!
Edit:
Here are three sample images, scanned from the same sheet of paper.
Have a look at HSV colourspace on Wikipedia - specifically this diagram.
It should be a better place to find the colour of your images, regardless of scanner and calibration.
Now, let's create a lime-green, yellow and cyan block and derive its colour using ImageMagick:
magick -size 100x100 xc:lime -colorspace HSV -channel 0 -separate -format "%[fx:mean*360]" info:
120
magick -size 100x100 xc:yellow -colorspace HSV -channel 0 -separate -format "%[fx:mean*360]" info:
60
magick -size 100x100 xc:magenta -colorspace HSV -channel 0 -separate -format "%[fx:mean*360]" info:
300
magick -size 100x100 xc:cyan -colorspace HSV -channel 0 -separate -format "%[fx:mean*360]" info:
180
Hopefully you can see we are correctly calculating the Hue angle. Now to your image. I have added an artificial frame so you can see how to remove the edges:
We can remove the frame like this:
magick YOURSCAN.jpg -gravity center -crop 80% cropped.jpg
So, my complete suggestion would be to crop and convert to HSV and check the mean Hue. You could also test if the image is fairly saturated so it doesn't pick up grey-ish, uncoloured images. You could also test the variance in the Hue channel to see if there are many different colours - or the spread of the hues is large and reject ones where it is large.
magick YOURSCAN.jpg -gravity center -crop 80% -colorspace HSV -channel 0 -separate -format "%[fx:mean*360]" info:
Just for reference, your 3 images come up with the following Hue angles on a scale of 0..360:
79, 68, 73
I would suggest you test a few more samples to establish a reasonable range.

Convert RGB to Grayscale in ImageMagick (but not the entire image)

I have the following bar chart:
I transformed it to greyscale using the following command:
convert image.tif -set colorspace Gray -separate -average image_greyscale.tif
and my result was
It is greyscale indeed, but the axes and the legend are grey as well. This is obvious now, but I'd like them to be black, like in the original image. Something like this:
How can I do it? Remake the bar charts again, with a greyscale palette, is not possible right now.
I think what you want to do in ImageMagick is simpler than your command. Just do
Input:
convert barchart.png -colorspace gray result.png
Result:
You can select the plum colour and change that to gray20 and then select the lime colour and change it to gray80 like this:
magick chart.png -fuzz 10% \
-fill gray20 -opaque "rgb(68,1,84)" \
-fill gray80 -opaque "rgb(122,209,81)" result.png
Or, as a one-liner:
magick chart.png -fuzz 10% -fill gray20 -opaque "rgb(68,1,84)" -fill gray80 -opaque "rgb(122,209,81)" result.png

Unable to create NDVI image using ImageMagick

I have four separate images - 2-projected.tif, 3-projected.tif, 4-projected.tif and 5-projected.tif. These are four Landsat images. Image 2-projected.tif corresponds to blue channel, image 3-projected.tif - to green channel, image 4-projected.tif - to red channel, and 5-projected.tif - to infrared. Now I want to create NDVI image. To do this, I first create a combined RGB image, using ImageMagic:
$ convert 4-projected.tif 3-projected.tif 2-projected.tif -combine RGB.tif
So far, so good. And then I try to follow a command from this tutorial, which is supposed to create NDVI image. I do it like so:
$ convert 5-projected.tif RGB.tif -channel RGB -fx '(u.r-v.r)/(u.r+v.r+0.001)' -normalize NDVI.tif
But as a result, I get these error messages:
convert: unable to parse expression (u.r-1.0*v.r)' # error/fx.c/FxGetSymbol/183
1.
convert: divide by zero'(u.r-1.0*v.r)/(u.r+v.r+0.001)'' # error/fx.c/FxEvaluat
eSubexpression/2159.
I'm not sure how can I fix it.
The two bands of interest are the red and the NIR and the formula for NDVI is:
NDVI = (NIR-red)/(NIR+red)
You have two options. First off, if you have the red and the NIR in two separate, single channel images, you can do:
convert red.tif NIR.tif -fx '(u.r-v.r)/(u.r+v.r+0.001)' -normalize -compress lzw NDVI.tif
Here, I am using u.r to refer to the first channel of the first image and v.r to refer to the first channel of the second image.
Alternatively, if the red and NIR are the first two channels in an RGB image (i.e. ImageMagick would call them the red and green channels):
convert RGB.tif -fx '(u.r-u.g)/(u.r+u.g+0.001)' -normalize -compress lzw NDVI.tif
Here I am using u.r to refer to the first channel of the first image and u.g to refer to the second channel of the first image.
The -fx method is extremely powerful, but notoriously slow. This method below should give you the same answer, but I have not checked it too thoroughly:
convert 4-projected.tif -write MPC:red +delete \
5-projected.tif -write MPC:NIR +delete \
\( mpc:red mpc:NIR -evaluate-sequence subtract \) \
\( mpc:red mpc:NIR -evaluate-sequence add \) \
-evaluate-sequence divide -normalize -compress lzw NDVI.tif
If you want to colourise the image with false colour, you could generate a Colour Lookup Table (CLUT) and map the grayscale values in the NDVI image to those colours. So, let's say you wanted to map the darkest blacks in your NDVI image to black, the quite dark values to red, the quite bright values to orange and the very brightest values to green, you could make a CLUT like this:
convert xc:black xc:red xc:orange xc:lime +append clut.png
and apply it the greyscale result from above like this:
convert NDVI.tif -normalize clut.png -clut falsecolour.jpg
If you want to make the orange and green tones longer (more prevalent), you can alter their lengths to make them longer in the CLUT:
convert -size 30x1 xc:black -size 40x1 xc:red -size 80x1 xc:orange -size 100x1 xc:lime +append clut.png
Then re-apply the CLUT:
convert NDVI.tif -normalize clut.png -clut result.jpg

How to select all grayscale colors?

In ImageMagick convert, I can select a specific color with e.g. -opaque blue. How can I select all grayscale colors (e.g. #000000, #707070, #ffffff)?
Not sure what you are trying to do, but this may help. The greyscale pixels will have a saturation of zero, so that is probably the easiest way to identify them.
First, make a funky sample image:
convert -size 400x100 gradient:black-white -bordercolor red -border 80 image.png
Now make all grey areas (those with very low saturation) transparent:
convert image.png -alpha on -channel A -fx "saturation<0.01?0:1" result.png
Note
Note that the -fx operator is extremely powerful but notoriously slow because it is actually interpolated for each and every pixel. If your images are large, the following technique may be more appropriate.
Basically, I clone the image and convert the whole thing to HSL colorspace and separate the channels. Then I discard the Hue and Lightness channels so I am left with just the Saturation. I then threshold that and copy that back to the original image as the alpha channel. On a 2000x2000 pixel image, this method will run in under a second whereas the -fx method will require 5-6 seconds.
convert image.png \( +clone -colorspace hsl -separate -delete 0,2 -threshold 1% \) -compose copy-opacity -composite result.png

How to output white png version of my image ?**

With ImageMagick shell command (convert?)...
Given a colorful input.png image.
How to us input.png to produce a white output.jpg version with similar dimensions an opacity of 100% ?
I will later on use this layer in my workflow.
This should work:
convert input.png -threshold -1 output.jpg
This transforms any pixel with an intensity greater than (-1), i.e., all of them, to white.
It does not work with GraphicsMagick, though, because in GM the threshold value is unsigned (in ImageMagick it's a signed "double"). Neither of the applications documents exactly what is supposed to happen when the threshold is negative.
Here's a command that works on both ImageMagick and GraphicsMagick, and is documented:
[gm] convert input.png -fuzz 100% -fill white -opaque gray output.jpg
You could use fill, like this:
convert input.png -fill "#ffffff" output.jpg
or
convert input.png -fill white output.jpg
Or you can convert all three channels (red, green and blue) to "1" which is full intensity, like this:
convert input.png -channel red -fx "1" -channel green -fx "1" -channel blue -fx "1" output.jpg

Resources