Irastris
Irastris

Reputation: 135

imagemagick: Remove all occurrences of a stray pixel surrounded by transparency

Hello! I'd like to remove all occurrences of stray pixels from a transparent image.

Below is an example image, enlarged for convenience:

Before Processing

Following is that same image but how I desire it to look after processing:

After Processing

The best way I can think to describe what I'm looking to achieve is that every pixel whose surrounding pixels are fully transparent should be removed. Think of the selector as a 3x3 grid, with the middle of the grid being the pixel operated on.

I took a look at Morphology in the IM manual, but it doesn't appear to provide a fine enough method for this.

Is this possible with ImageMagick? Is there any other command line software that could achieve this if not?

Upvotes: 3

Views: 1596

Answers (2)

fmw42
fmw42

Reputation: 53081

In Imagemagick, you can use -connected-components to remove those isolated blocks of pixels. They seem to be 5x5 pixels on a side. So we threshold the area to keep at 26. We remove those blocks in the alpha channel and then replace that in the image. (Note we need to use 8-connected vs 4-connected region detection to preserve your other regions).

Since you say the your image was enlarged, so I presume your isolated regions were 1x1 pixels. So change the area-threshold to 2, to remove single pixel regions.

Input:

enter image description here

Unix Syntax:

convert img.png \
\( -clone 0 -alpha extract -type bilevel \
-define connected-components:mean-color=true \
-define connected-components:area-threshold=26 \
-connected-components 8 \) \
-alpha off -compose copy_opacity -composite \
result.png


enter image description here

Windows Syntax:

convert img.png ^
( -clone 0 -alpha extract -type bilevel ^
-define connected-components:mean-color=true ^
-define connected-components:area-threshold=26 ^
-connected-components 8 ) ^
-alpha off -compose copy_opacity -composite ^
result.png


See -connected-components

ADDITION:

If you only want to remove the small isolated color pixels and not any transparent pixels inside the color ones, then there is no trivial way to do that. That is an enhancement I would like to have. However, it can be done.

Here is your image modified so that the top left red block has a single transparent center pixel. I added a red line to its right to be sure it was larger than 25 pixels when the center was turned transparent and so that you could see which pixel has the transparent center. You will have to download and zoom in on this image to see the missing pixel.

enter image description here

4x Zoom:

enter image description here

The method is to find all white regions in the alpha channel and then make a list of all regions that are less than 26 pixels. Then reprocess the image to remove those regions by ID.

Get ID List

id_list=""
OLDIFS=$IFS
IFS=$'\n'
arr=(`convert img2.png -alpha extract -type bilevel \
-define connected-components:mean-color=true \
-define connected-components:verbose=true \
-connected-components 8 null: | grep "gray(255)" | sed 's/^[ ]*//'`)
echo "${arr[*]}"
num=${#arr[*]}
IFS=$OLDIFS
for ((i=0; i<num; i++)); do
id=`echo "${arr[$i]}" | cut -d' ' -f1 | sed 's/[:]*$//'`
count=`echo "${arr[$i]}" | cut -d' ' -f4`
if [ $count -lt 26 ]; then
id_list="$id_list $id"
fi
done
echo "$id_list"


Here is what is printed

12: 5x5+120+70 122.0,72.0 25 gray(255)
14: 5x5+30+85 32.0,87.0 25 gray(255)
15: 5x5+110+85 112.0,87.0 25 gray(255)
16: 5x5+75+90 77.0,92.0 25 gray(255)
17: 5x5+40+100 42.0,102.0 25 gray(255)
18: 5x5+110+110 112.0,112.0 25 gray(255)
19: 5x5+140+110 142.0,112.0 25 gray(255)
21: 5x5+15+130 17.0,132.0 25 gray(255)
22: 5x5+40+140 42.0,142.0 25 gray(255)
23: 5x5+85+140 87.0,142.0 25 gray(255)
24: 5x5+120+140 122.0,142.0 25 gray(255)
2: 5x5+55+5 57.0,7.0 25 gray(255)
5: 5x5+100+20 102.0,22.0 25 gray(255)
7: 5x5+65+30 67.0,32.0 25 gray(255)
8: 5x5+125+30 127.0,32.0 25 gray(255)
9: 5x5+105+50 107.0,52.0 25 gray(255)
11: 5x5+25+65 27.0,67.0 25 gray(255)

12 14 15 16 17 18 19 21 22 23 24 2 5 7 8 9 11

Reprocess to remove regions by ID

convert img2.png \
\( -clone 0 -alpha extract -type bilevel \
-define connected-components:mean-color=true \
-define connected-components:remove="$id_list" \
-connected-components 8 -background black -flatten +write tmp.png \) \
-alpha off -compose copy_opacity -composite \
result2.png


enter image description here

4x Zoom:

enter image description here

Upvotes: 7

jcupitt
jcupitt

Reputation: 11190

fmw42's excellent answer uses connected regions, but I think it is possible with just a morphology. Use:

0 0 0 
0 1 0 
0 0 0 

As the structuring element with erode and it'll detect 8-way connected isolated pixels. Now EOR that with your alpha and it'll make those pixels fully transparent (ie. remove them).

I don't know IM well enough to make you a command to do this :-( But with the libvips command-line it would be this to make a test image:

size=256

# sparse speckles for the alpha
vips gaussnoise t1.v $size $size
vips relational_const t1.v a.v more 200

# RGB noise 
vips gaussnoise r.v $size $size
vips gaussnoise g.v $size $size
vips gaussnoise b.v $size $size

# assemble and save
vips bandjoin "r.v g.v b.v a.v" x.png

Then to remove your stray pixels:

# make mask.mor, a file containing our structuring element
cat >mask.mor <<EOF
3 3 
0   0   0
0   255 0
0   0   0
EOF

# pull out the alpha channel
vips extract_band x.png a.v 3

# find isolated pixels
vips morph a.v t1.v mask.mor erode

# EOR with alpha to zap those pixels
vips boolean t1.v a.v t2.v eor

# extract rgb from original, then attach our modified alpha
vips extract_band x.png rgb.v 0 --n 3
vips bandjoin "rgb.v t2.v" x2.png

Here's before and after:

enter image description here

The libvips CLI is fast but a bit clumsy. It's neater if you use something like Python to script it.

Upvotes: 4

Related Questions