Reputation: 22643
Is there anything in imagemagick or gimp or other linux compatible tool what can automatically detect individual objects on image and either return some location of object or store each object as separate image?
I have image like this one:
For other images where objects are located on a grid, I have successfully used crop
operator in imagemagick, e.g. for 3x3 grid:
convert -crop 3x3@ in-image.jpg out-image-%d.jpg
I cannot use crop
when there is no rectangular grid, but I thought that white color should be enough for objects separation.
Upvotes: 17
Views: 5305
Reputation: 8350
With GIMP, you can do it the following way:
Magic Wand
(Select Contiguous Regions tool) and select your background;Select
> Invert
to get multiple selections;export-selected-regions
below if you don't have it yet;Select
> Export Selected Regions
, your new images should be in the same folder as the original one.If you're using GIMP 2.10
on mac
, put the script below in: ~/Library/Application\ Support/GIMP/2.10/scripts/export-selected-regions.scm
, check for the appropriate folder for other system.
;;; Non-interactively save all selected regions as separate files
(define (script-fu-export-selected-regions image drawable)
;; Start
(gimp-image-undo-group-start image)
;; If there are selections
(when (= 0 (car (gimp-selection-is-empty image)))
(let ((number 1) (prefix "") (suffix ""))
;; Construct filename components
(let* ((parts (strbreakup (car (gimp-image-get-filename image)) "."))
(coextension (unbreakupstr (reverse (cdr (reverse parts))) "."))
(extension (cadr parts)))
(set! prefix (string-append coextension "_selection-" ))
(set! suffix (string-append "." extension)))
;; Convert all selections to a single path
(plug-in-sel2path RUN-NONINTERACTIVE image drawable)
;; For each stroke in the path
(let ((vectors (vector-ref (cadr (gimp-image-get-vectors image)) 0)))
(for-each (lambda (stroke)
;; Convert the stroke back into a selection
(let ((buffer (car (gimp-vectors-new image "buffer")))
(points (gimp-vectors-stroke-get-points vectors stroke)))
(gimp-image-insert-vectors image buffer 0 -1)
(apply gimp-vectors-stroke-new-from-points buffer points)
(gimp-vectors-to-selection buffer 2 TRUE FALSE 0 0)
(gimp-image-remove-vectors image buffer))
;; Replace the selection with its bounding box
(apply (lambda (x0 y0 x1 y1)
(gimp-image-select-rectangle image 2 x0 y0 (- x1 x0) (- y1 y0)))
(cdr (gimp-selection-bounds image)))
;; Extract and save the contents as a new file
(gimp-edit-copy drawable)
(let* ((image (car (gimp-edit-paste-as-new)))
(drawable (car (gimp-image-get-active-layer image)))
(filename ""))
(while (or (equal? "" filename) (file-exists? filename))
(let* ((digits (number->string number))
(zeros (substring "0000" (string-length digits))))
(set! filename (string-append prefix zeros digits suffix)))
(set! number (+ number 1)))
(gimp-file-save RUN-NONINTERACTIVE image drawable filename filename)
(gimp-image-delete image)))
(vector->list (cadr (gimp-vectors-get-strokes vectors))))
(gimp-image-remove-vectors image vectors))))
;; End
(gimp-selection-none image)
(gimp-image-undo-group-end image))
(script-fu-register "script-fu-export-selected-regions"
"Export Selected Regions"
"Export each selected region to a separate file."
"Andrew Kvalheim <[email protected]>"
"Andrew Kvalheim <[email protected]>"
"2012"
"RGB* GRAY* INDEXED*"
SF-IMAGE "Image" 0
SF-DRAWABLE "Drawable" 0)
(script-fu-menu-register "script-fu-export-selected-regions" "<Image>/Select")
Many thanks to Andrew Kvalheim for his script-fu.
Upvotes: 0
Reputation: 207465
I would tackle this with a "Connected Components Analysis", or "Image Segmentation" approach, like this...
First, split the input image into components, specifying a minimum size (in order to remove smaller lumps) and allowing for 8-connectivity (i.e. the 8 neighbouring pixels N, NE, E, SE, S, SW, W, NW are considered neighbours) rather than 4-connectivity - which only considers N, E, S and W pixels connected.
convert https://i.sstatic.net/T2VEJ.jpg -threshold 98% \
-morphology dilate octagon \
-define connected-components:area-threshold=800 \
-define connected-components:verbose=true \
-connected-components 8 -auto-level PNG8:lumps.png
which gives this output:
Objects (id: bounding-box centroid area mean-color):
0: 450x450+0+0 219.2,222.0 93240 srgb(255,255,255)
14: 127x98+111+158 173.0,209.4 9295 srgb(0,0,0)
29: 105x91+331+303 384.1,346.9 6205 srgb(0,0,0)
8: 99x75+340+85 388.9,124.6 5817 srgb(1,1,1)
15: 110x69+330+168 385.4,204.9 5640 srgb(1,1,1)
3: 114x62+212+12 270.0,42.4 5021 srgb(0,0,0)
4: 103x63+335+12 388.9,44.9 4783 srgb(0,0,0)
11: 99x61+13+134 61.5,159.1 4181 srgb(0,0,0)
37: 128x52+313+388 375.1,418.4 4058 srgb(0,0,0)
24: 95x62+24+256 69.6,285.7 4017 srgb(0,0,0)
2: 91x68+15+12 62.0,44.4 3965 srgb(0,0,0)
38: 91x50+10+391 55.1,417.0 3884 srgb(0,0,0)
12: 83x64+249+134 288.3,168.4 3761 srgb(0,0,0)
19: 119x62+320+240 385.4,268.4 3695 srgb(9,9,9)
25: 93x63+128+268 176.1,302.1 3612 srgb(0,0,0)
39: 96x49+111+391 158.1,416.0 3610 srgb(0,0,0)
31: 104x59+117+333 172.9,360.1 3493 srgb(0,0,0)
33: 88x55+238+335 279.3,364.5 3440 srgb(0,0,0)
26: 121x54+230+271 287.6,294.0 3431 srgb(8,8,8)
1: 98x61+109+11 159.7,40.0 3355 srgb(0,0,0)
40: 88x42+218+399 262.3,419.7 3321 srgb(0,0,0)
6: 87x61+115+70 157.9,100.1 3263 srgb(0,0,0)
30: 97x57+14+327 57.3,357.2 3237 srgb(55,55,55)
17: 84x57+13+207 53.1,232.2 2995 srgb(0,0,0)
5: 107x58+10+68 58.9,97.5 2988 srgb(0,0,0)
18: 77x60+237+212 273.0,243.0 2862 srgb(0,0,0)
7: 87x49+249+78 291.8,99.3 2703 srgb(9,9,9)
10: 82x51+178+109 222.8,133.9 2628 srgb(0,0,0)
Each line corresponds to a separate component, or segment, and shows the widths and heights of the bounding boxes for each component, and their offsets from the top-left corner. You can parse that easily enough with awk
and draw the indicated red boxes onto the image to give this:
The output image is called lumps.png
and it looks like this:
and you can see that each component (piece of meat) has a different grey level associated with it. You can also analyse lumps.png
, and extract a separate mask for each piece of meat, like this:
#!/bin/bash
# Extract every grey level, and montage together all that are not entirely black
rm mask_*png 2> /dev/null
mask=0
for v in {1..255}; do
((l=v*255))
((h=l+255))
mean=$(convert lumps.png -black-threshold "$l" -white-threshold "$h" -fill black -opaque white -threshold 1 -verbose info: | grep -c "mean: 0 ")
if [ "$mean" -eq 0 ]; then
convert lumps.png -black-threshold "$l" -white-threshold "$h" -fill black -opaque white -threshold 1 mask_$mask.png
((mask++))
fi
done
That gives us masks like this:
and this
we can see them all together if we do this
montage -tile 4x mask_* montage_masks.png
If we now apply each of the masks to the input image as the opacity and trim the resulting image, we will be left with the individual lumps of meat
seg=0
rm segment_*png 2> /dev/null
for f in mask_*png; do
convert https://i.sstatic.net/T2VEJ.jpg $f -compose copy-opacity -composite -trim +repage segment_$seg.png
((seg++))
done
And they will look like this:
and this
Or, we can put them all together like this:
montage -background white -tile 4x segment_* montage_results.png
Cool question :-)
Upvotes: 16
Reputation: 1331
It can be done with ImageMagick in multiple steps. The orignal image is named meat.jpg:
convert meat.jpg -threshold 98% -morphology Dilate Octagon meat_0.png
convert meat_0.png text: | grep -m 1 black
This gives you a pixel location in the area of the first part of meat:
131,11: ( 0, 0, 0) #000000 black
We'll use this to color the first piece in red, separate the red channel and then create and apply the mask for the first piece:
convert meat_0.png -fill red -bordercolor white \
-draw 'color 131,11 filltoborder' meat_1_red.png
convert meat_1_red.png -channel R -separate meat_1.png
convert meat_1_red.png meat_1.png -compose subtract \
-threshold 50% -composite -morphology Dilate Octagon \
-negate meat_1_mask.png
convert meat_1_mask.png meat.jpg -compose Screen -composite \
-trim meat_1.jpg
The resulting meat_1.jpg is already trimmed. You can then proceed the same way with meat_1.png in stead of meat_0.png, generating meat_2.png as the basis for successive iterations on the fly. Maybe this can be further simplified and wrapped in a shell script.
Upvotes: 9