Reputation: 123
This is a more advanced version of this question. From this image, how would I get the average color value of the pixels under the triangle from its co-ordinates with ImageMagick/GraphicsMagick/a different command-line tool? Preferably the solution should work for other polygons as well.
Upvotes: 2
Views: 984
Reputation: 123
I ended up solving my problem by using the scikit-image and numpy python modules. Example code is as follows:
from skimage import io, draw
import numpy
# read in the image as a numpy array
image = io.imread("image.png")
# setup a list of coords in the polygon
polygon = numpy.array([[100, 100], [200, 100], [200, 200], [100, 200]])
# Get a 2D list of pixels in the polygon
pixels = image[draw.polygon(polygon[:, 1], polygon[:, 0])]
# Use the channels of each pixel to get averages and convert them to ints
channels = numpy.average(pixels, 0).astype(int)
# Print!
print(channels)
Upvotes: 1
Reputation: 207798
Kind of putting some meat on the bones of @Bonzo's answer, here is one way you could do it.
convert image.jpg \
\( +clone -evaluate set 0 -fill white \
-draw "polygon 400,50 450,150 200,220" \
-write mask.png -transparent black \
\) -compose copyopacity -composite \
-write masked.png -resize 1x1! \
-alpha off -depth 8 -format "%[pixel:p{0,0}]\n" info:
srgb(145,114,94)
So, that says... "load up your original image, and do the following stuff in parentheses on the side, away from the main image. Copy the image (-clone
) and make it all black (-evaluate set 0
), and make the fill colour white (-fill white
) for the polygon we are about to draw. Draw the polygon (-draw "polygon ..."
) and save it in a file called mask.png
. Now make all black areas of the image transparent. Good, we have a mask. Now go back to the original image by closing the parentheses. Copy the transparency from our mask image and apply it to our original image (-compose copyopacity -composite
). Write the masked image out as masked.png
. Resize to 1x1 pixel to average it. Now turn the transparency off and make that single pixel solid and write on the terminal what it is in human-readable terms."
You can see the intermediate images - here is mask.png
:
and here is masked.png
:
Yes, I know it doesn't match exactly, but that's only because you didn't supply the coordinates and I had to guess them! Put your own numbers into the -draw polygon
part and it will work exactly.
You don't actually need either of the intermediate images, I only saved them for debugging and demonstration purposes, so you can remove -write mask.png
and also -write masked.png
from the command above.
Verification
Just to check the accuracy of the rounding and averaging, you can calculate the average colour mathematically. You use the same basic technique as above, but calculate the scaling factor for the mean by working out the proportion of the pixels that are white in the mask and then mask the image and look at the means of R,G and B in the masked image and scale them in proportion...
convert -respect-parentheses image.jpg \
\( +clone -evaluate set 0 -fill white \
-draw "polygon 400,50 450,150 200,220" \
-format "Factor:%[fx:1/mean]\n" -write info: \
\) -compose multiply -composite -verbose info: \
| grep -E "Factor:|Red:|Green:|Blue:|mean:"
Factor:21.4019
Red:
mean: 6.70078 (0.0262776)
Green:
mean: 5.31762 (0.0208534)
Blue:
mean: 4.40595 (0.0172782)
# Check the Red value - original method gave srgb(145,114,94)
echo "6.70078*21.4019"|bc
143.40942
# Check the Green value
echo "5.317*21.4019"|bc
113.7939
# Check the Blue value
echo "4.40595*21.4019"|bc
94.29570
By the way, that average works out at this:
convert -size 100x100 xc:"srgb(145,114,94)" average.png
Upvotes: 2
Reputation: 5299
I would use your coordinates to create a mask of the area of interest using -draw. This can then be then used to convert the area outside of the shape to transparent. Then as before reduce the image to a 1x1 pixel.
Upvotes: 1