Search code examples
image-processingimagemagickimage-manipulationimagemagick-convert

Imagemagick: Are there any operators that could convert all spots on b/w mages into pixel placed at geometric center?


I have a huge set of black and white images with spots and want to replace every spot into pixel (or small circle) that is located in the geometric center of the spot.

enter image description here

Because the spots has different sizes, I could not consequently use dilate operator because it completely deletes small sports. Is there any way to do this automatical with Imagemagick?


Solution

  • Kudos (and upvotes) to Fred (@fmw42) for the original technique. As much for my own curiosity as anything (and to document the approach), I wanted to make a variant that is hopefully more CPU-friendly, I/O friendly and maybe more portable. This should also have advantages given that OP has large numbers of images to process.

    I worked on the following aspects:

    • rather than repeatedly load the image, draw a circle and re-save for every circle, I wanted to use a script that loads it once, draws all the circles and saves it

    • reduce dependency on other tools, and their process creation times - so all the cut, grep, tr, convert, echo and so on are encapsulated inside a single, awk invocation leveraging its built-in ability to split fields, process text, and do math. Hopefully this makes it easier to port to Windows too as fewer binaries are needed.

    So, it looks like this:

    #!/bin/bash
    
    magick black_spots.png \
       -threshold 50% -type bilevel \
       -define connected-components:mean-color=true \
       -define connected-components:area-threshold=0-300 \
       -define connected-components:verbose=true \
       -connected-components 8 null: | awk -F'[ x+]*' '
          BEGIN       { print "black_spots.png -fill white -colorize 100 -fill black" }
          /gray\(0\)/ {
                        w=$3; h=$4; x=$5; y=$6; cx=x+w/2; cy=y+h/2
                        printf("-draw \"translate %f,%f circle 0,0 0,5\"\n", cx, cy)
                      }
          END         { print "-write result.png"}
       ' | magick -script -
    

    enter image description here


    The awk part in the middle actually generates a script that looks like this:

    black_spots.png -fill white -colorize 100 -fill black
    -draw "translate 1429.000000,368.000000 circle 0,0 0,5"
    -draw "translate 6.000000,1026.500000 circle 0,0 0,5"
    -draw "translate 739.500000,378.000000 circle 0,0 0,5"
    ...
    ...
    -write result.png
    

    That is then piped into magick -script at the end. Hopefully it is clear that the input file is only read once, all the circles are drawn, then the output file is written - just once.


    Some notes on the awk parts:

    • -F'[ x+]*' means that multiple spaces, the letter x and + signs should all be treated as field separators

    • BEGIN and END blocks are executed once at the start and end of the awk script

    • the /gray\(0\)/ block is executed only on lines containing gray(0)


    As regards processing large numbers of files, I would use GNU Parallel, but you have not indicated your operating system. Basically, modify the above script to accept a filename as a parameter, save it as ProcessOne, make it executable with chmod +x ProcessOne, then run:

    parallel ./ProcessOne ::: *.png
    

    and it will keep all your CPU cores busy processing all your files in parallel till they are all done. You can get progress bars and ETAs with various switches:

    parallel --eta ...         # show ETA
    parallel --progress ...    # report progress
    parallel --bar ...         # add progress bar
    parallel -j 4 ...          # just run 4 jobs in parallel