Search code examples
image-processingimagemagickimage-manipulation

Replace color of region by height imagemagick


With command "convert" I know how to replace the color of a region having the coordinates and size, but is there a way to replace within an image, all regions that have for example 40 pixel in height? Thanks

This would be an example of input image where there 4 green rectangles with 40 pixels in height.

Input.png

And this would be the output where those 4 green rectangles were replace to black knowing their height but not their coordinates, if possible.

Out.png


Solution

  • I will try to help Mark get finished. I use ImageMagick and some unix code as follows with Imagemagick's Connected Components Labeling (-connected-components).

    Here is the simple connect components results for all colors in your image:

    convert in-1.png \
    -define connected-components:verbose=true \
    -connected-components 4 \
    null:
    
    Objects (id: bounding-box centroid area mean-color):
      0: 256x256+0+0 133.6,134.1 50820 srgb(255,255,255)
      1: 86x40+23+30 65.5,49.5 3440 srgb(0,127,70)
      6: 60x40+42+126 71.5,145.5 2400 srgb(0,127,70)
      4: 86x27+22+80 64.5,93.0 2322 srgb(0,38,255)
      5: 86x27+121+121 163.5,134.0 2322 srgb(0,127,70)
      2: 37x40+127+59 145.0,78.5 1480 srgb(0,127,70)
      3: 36x40+177+59 194.5,78.5 1440 srgb(0,127,70)
      7: 41x32+89+186 109.0,201.5 1312 srgb(255,106,0)
    


    Note that none of your green, i.e, srgb(0,127,70) have heights above 40. All are 40 and one is 27. So to demonstrate, lets get all boxes greater than 30.

    To the above code, I first select all the green objects, remove the leading space, extract the bounding box, which is field 2 and then change the x to a +.

    Then I loop over each bounding box and extract the ht, and top left corner xx and yy values. I test the ht against the htval=30 and if it passes, I flood fill the green with black.

    htval=30
    convert in-1.png in-1_result.png
    bboxArr=(`convert in-1.png \
    -define connected-components:verbose=true \
    -connected-components 4 \
    null: | grep "srgb(0,127,70)" | sed 's/^[ ]*//' | cut -d\  -f2 | tr "x" "+"`)
    num=${#bboxArr[*]}
    for ((i=0; i<num; i++)); do
    ht=`echo ${bboxArr[$i]} | cut -d+ -f2`
    xx=`echo ${bboxArr[$i]} | cut -d+ -f3`
    yy=`echo ${bboxArr[$i]} | cut -d+ -f4`
    if [ $ht -gt $htval ]; then
    convert in-1_result.png -fill black -draw "color $xx,$yy floodfill" -alpha off in-1_result.png
    fi
    done
    


    enter image description here

    Note in the above, the line

    null: | grep "srgb(0,127,70)" | sed 's/^[ ]*//' | cut -d\  -f2 | tr "x" "+"`)
    


    could be replaced with

    null: | awk '/srgb\(0,127,70\)/ && sub(/x/, "+") {print $2}'
    

    ADDITION:

    Here is a more compact method using awk to do all the filtering and save the output as color x,y floodfill. Then only one draw command is needed to do the processing.

    convert in-1.png in-1_result.png
    floodfill_arr=(`convert in-1.png \
    -define connected-components:verbose=true \
    -connected-components 4 \
    null: | awk '/srgb\(0,127,70\)/ && sub(/[x]/, "+") && split($2, arr, "+") {if (arr[4]>30) {print " color " arr[3] "," arr[4] " floodfill"}}'`)
    echo "${floofill_arr[*]}"
    
    color 42,126 floodfill color 121,121 floodfill color 127,59 floodfill color 177,59 floodfill
    
    convert in-1_result.png -fill black -draw "${floodfill_arr[*]}" -alpha off in-1_result.png
    


    The awk first finds all lines with the color green, then replaces any x with +, then splits field $2 into array (arr) parts using field separator +, then tests if the 4th arr field (ht) is larger than 30 and if so, then prints the -draw command for each bounding box that passes the test.