[Image-SIG] Unsharp Masking?

Kevin@Cazabon.com kevin@cazabon.com
Sun, 17 Mar 2002 16:18:12 -0700


This is a multi-part message in MIME format.

------=_NextPart_000_000F_01C1CDCF.55D6CF30
Content-Type: multipart/alternative;
	boundary="----=_NextPart_001_0010_01C1CDCF.55D6CF30"


------=_NextPart_001_0010_01C1CDCF.55D6CF30
Content-Type: text/plain;
	charset="iso-8859-1"
Content-Transfer-Encoding: quoted-printable

Does anyone have a FAST unsharp-masking method for use with PIL?

I wrote a brute-force module for this in Python today (attached, if it =
gets through the newsgroup filters) that works just fine, but it's =
mighty slow... anyone have something faster?  The built in =
ImageEnhance.Sharpness filter is OK for very basic stuff, but the best =
way to sharpen is through an unsharp-mask type algorithm... it's much =
more powerful and controllable.

For those that don't know how unsharp masking works, here's the basics:

1)  a copy of the image is blurred using a gaussian-type blurring =
algorighm (this is the 'unsharp' part), with a user-defined radius for =
the blur.

2)  the blurred image is then compared (pixel by pixel if necessary) to =
the original image

3)  the amount of difference between the blurred pixel and original =
pixel determines if it is an edge or not.  If the difference is greater =
than the user-specified "threshold", sharpening is performed in step 4)

4)  the pixel is changed by the OPPOSITE amount of the difference =
between the original and blurred pixels, multiplied by the user-defined =
"percentage"



Any help is appreciated in speeding this up!

Kevin Cazabon.
------=_NextPart_001_0010_01C1CDCF.55D6CF30
Content-Type: text/html;
	charset="iso-8859-1"
Content-Transfer-Encoding: quoted-printable

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<HTML><HEAD>
<META http-equiv=3DContent-Type content=3D"text/html; =
charset=3Diso-8859-1">
<META content=3D"MSHTML 5.50.4913.1100" name=3DGENERATOR>
<STYLE></STYLE>
</HEAD>
<BODY bgColor=3D#ffffff>
<DIV><FONT face=3DArial size=3D2>Does anyone have a FAST unsharp-masking =
method for=20
use with PIL?</FONT></DIV>
<DIV><FONT face=3DArial size=3D2></FONT>&nbsp;</DIV>
<DIV><FONT face=3DArial size=3D2>I wrote a brute-force module for this =
in Python=20
today&nbsp;(attached, if it gets through the newsgroup =
filters)&nbsp;that works=20
just fine, but it's mighty slow... anyone have something faster?&nbsp; =
The built=20
in ImageEnhance.Sharpness filter is OK for very basic stuff, but the =
best way to=20
sharpen is through an unsharp-mask type algorithm... it's much more =
powerful=20
and&nbsp;controllable.</FONT></DIV>
<DIV><FONT face=3DArial size=3D2></FONT>&nbsp;</DIV>
<DIV><FONT face=3DArial size=3D2>For those that don't know how unsharp =
masking=20
works, here's the basics:</FONT></DIV>
<DIV><FONT face=3DArial size=3D2></FONT>&nbsp;</DIV>
<DIV><FONT face=3DArial size=3D2>1)&nbsp; a copy of the image is blurred =
using a=20
gaussian-type blurring algorighm (this is the 'unsharp' part), with a=20
user-defined radius for the blur.</FONT></DIV>
<DIV><FONT face=3DArial size=3D2></FONT>&nbsp;</DIV>
<DIV><FONT face=3DArial size=3D2>2)&nbsp; the blurred image is then =
compared (pixel=20
by pixel if necessary) to the original image</FONT></DIV>
<DIV><FONT face=3DArial size=3D2></FONT>&nbsp;</DIV>
<DIV><FONT face=3DArial size=3D2>3)&nbsp; the amount of difference =
between the=20
blurred pixel and original pixel determines if it is an edge or =
not.&nbsp; If=20
the difference is greater than the user-specified "threshold", =
sharpening is=20
performed in step 4)</FONT></DIV>
<DIV><FONT face=3DArial size=3D2></FONT>&nbsp;</DIV>
<DIV><FONT face=3DArial size=3D2>4)&nbsp; the pixel is changed by the =
OPPOSITE=20
amount of the difference between the original and blurred pixels, =
multiplied by=20
the user-defined "percentage"</FONT></DIV>
<DIV><FONT face=3DArial size=3D2></FONT>&nbsp;</DIV>
<DIV><FONT face=3DArial size=3D2></FONT>&nbsp;</DIV>
<DIV><FONT face=3DArial size=3D2></FONT>&nbsp;</DIV>
<DIV><FONT face=3DArial size=3D2>Any help is appreciated in speeding =
this=20
up!</FONT></DIV>
<DIV><FONT face=3DArial size=3D2></FONT>&nbsp;</DIV>
<DIV><FONT face=3DArial size=3D2>Kevin =
Cazabon.</FONT></DIV></BODY></HTML>

------=_NextPart_001_0010_01C1CDCF.55D6CF30--

------=_NextPart_000_000F_01C1CDCF.55D6CF30
Content-Type: text/plain;
	name="UnsharpMaskingModule.py"
Content-Transfer-Encoding: quoted-printable
Content-Disposition: attachment;
	filename="UnsharpMaskingModule.py"

# EditStation -- Unsharp Masking
# by Kevin Cazabon ( kevin@cazabon.com )
# Copyright 2002, all rights reserved

######################################################################
# Constants
######################################################################

######################################################################
# Imports
######################################################################

import Image
import array

######################################################################
# Functions
######################################################################

def unsharpMask (image, radius, weight, threshold):
    # perform a gaussian blur on the image, using radius as a PERCENTAGE =
of the image size.
    # this will allow realistic previewing on smaller versions of the =
same image.

    # radius is the size of matrix to use for blurring
    # weight is the percentage of how much of each out-lying pixel is =
taken into account,
    #   each, 'ring' is decreased by the same percentage.
    #
    # example:  radius 5, weight 90 would result in a matrix like the =
following:
    #
    #           66      73      81      73      66=20
    #           73      81      90      81      73=20
    #           81      90      100     90      81=20
    #           73      81      90      81      73=20
    #           66      73      81      73      66
    #
    # then, use the difference between the orignal pixel and the new =
value to determine how much to increase
    # the edge contrast.

    radius =3D float(image.size[0] * image.size[1] * radius) / 300000.0

    weight =3D float(weight)/100.0

    imArray =3D array.array('B', image.tostring())

    if image.mode =3D=3D "RGB":
        channels =3D 3
    elif image.mode =3D=3D "CMYK":
        channels =3D 4
    elif image.mode =3D=3D "L":
        channels =3D 1
    else:   # cant process other mode images
        return image

    newImage =3D Image.new(image.mode, image.size)

    for column in range(image.size[0]):
        for row in range(image.size[1]):
            newPixel =3D []
            for c in range (channels):
                totalValue =3D 0
                origPixel =3D imArray[(column + (row * image.size[0])) * =
channels + c]

                for xr in range(-int(radius/2), int(radius/2), 1):
                    for yr in range(-int(radius/2), int(radius/2), 1):
                        pixNumber =3D ((column + (row * image.size[0])) =
* channels) + c + (xr * channels) + (yr * image.size[0] * channels)
                       =20
                        if pixNumber < 0:
                            pixNumber =3D column * channels
                        elif pixNumber > len(imArray) - 1:
                            pixNumber =3D (column + (row * =
image.size[0])) * channels
                           =20
                        pixValue =3D imArray[pixNumber]
                       =20
                        pixWeight =3D pow(weight, (abs(xr) + abs(yr)))
                        totalValue =3D totalValue + ((origPixel - =
pixValue) * pixWeight)

                newValue =3D int(totalValue / (radius*radius) + 0.5)
               =20
                if abs(origPixel - newValue) <=3D threshold:
                    newValue =3D origPixel
                   =20
                newValue =3D origPixel + newValue
                newPixel.append(newValue)
               =20
            newImage.putpixel((column, row), tuple(newPixel))
   =20
    return newImage


######################################################################
# Self Test
######################################################################

if __name__ =3D=3D "__main__":
    im =3D Image.open("c:\\temp\\testsmall.tif")
    im.show()
    radius =3D 5
    weight =3D 100
    threshold =3D 3.0
   =20
    im =3D unsharpMask(im, radius, weight, threshold)
    im.show()
------=_NextPart_000_000F_01C1CDCF.55D6CF30--