Image embossing in Python with PIL -- adding depth, azimuth, etc

image-processing, python, python-imaging-library

Solution

If you cannot achieve your goal by using or combination of operations (like rotating, then applying the EMBOSS filter, the re-rotating), (or enhancing the contrast then embossing) then you may resort to changing (or creating your own) filter matrix.

Within ImageFilter.py you will find this class:

##
# Embossing filter.

class EMBOSS(BuiltinFilter):
    name = "Emboss"
    filterargs = (3, 3), 1, 128, (
        -1,  0,  0,
        0,  1,  0,
        0,  0,  0
        )

Placing a -1 in a different corner of the matrix will change the azimuth and making it a -2 may have the effect you are looking for.

The matrix is applied pixel-by-pixel. Each element in the matrix corresponds to the current pixel and surrounding pixels; the center value representing the current pixel. The new, transformed current pixel will be created as a combination of all 9 pixels, weighted by the values in the matrix. For example, a matrix with all zeros and a 1 in the center will not change the image.

Additional parameters are `scale` and `offset`. For the built-in EMBOSS, the values are 1 (scale) and 128 (offset). Changing these will change the overall strength of the result.

From ImageFilter.py:

# @keyparam scale Scale factor.  If given, the result for each
#    pixel is divided by this value.  The default is the sum
#    of the kernel weights.
# @keyparam offset Offset.  If given, this value is added to the
#    result, after it has been divided by the scale factor.

As I am unfamiliar with the effects of GIMP's "depth" parameter, I cannot say which is most likely to do what you want.

You can also make the matrix a different size. Replace the (3,3) with (5,5), and then create 25-element matrix.

To make temporary changes to the filter without re-saving source code, just do this:

ImageFilter.EMBOSS.filterargs=((3, 3), 1, 128, (-1, 0, 0, 0, 1, 0, 0, 0, 0))

Edit: (taking the NumPy approach)

from PIL import Image
import numpy

# defining azimuth, elevation, and depth
ele = numpy.pi/2.2 # radians
azi = numpy.pi/4.  # radians
dep = 10.          # (0-100)

# get a B&W version of the image
img = Image.open('daisy.jpg').convert('L') 
# get an array
a = numpy.asarray(img).astype('float')
# find the gradient
grad = numpy.gradient(a)
# (it is two arrays: grad_x and grad_y)
grad_x, grad_y = grad
# getting the unit incident ray
gd = numpy.cos(ele) # length of projection of ray on ground plane
dx = gd*numpy.cos(azi)
dy = gd*numpy.sin(azi)
dz = numpy.sin(ele)
# adjusting the gradient by the "depth" factor
# (I think this is how GIMP defines it)
grad_x = grad_x*dep/100.
grad_y = grad_y*dep/100.
# finding the unit normal vectors for the image
leng = numpy.sqrt(grad_x**2 + grad_y**2 + 1.)
uni_x = grad_x/leng
uni_y = grad_y/leng
uni_z = 1./leng
# take the dot product
a2 = 255*(dx*uni_x + dy*uni_y + dz*uni_z)
# avoid overflow
a2 = a2.clip(0,255)
# you must convert back to uint8 /before/ converting to an image
img2 = Image.fromarray(a2.astype('uint8')) 
img2.save('daisy2.png')

I hope this helps. I can see now why you were disappointed with PIL's results. Wolfram Mathworld is a good resource for a vector algebra refresher.

Before

After

Problem

I am trying to emboss an image using PIL. PIL provides a basic way to emboss an image ( using `ImageFilter.EMBOSS`). In image editing packages like GIMP, you can vary parameters like Azimuth, depth and elevation in this embossed image. How to do this with PIL? At the very least I want to adjust the "depth" of the embossed image. Update: I tried things suggested by Paul (modifying the `filterargs`such as `scale, offset` and the matrix), but I couldn't change the "depth" effect. So still looking for an answer. Here is the comparison of embossing effect using PIL (left) and GIMP (right). The original picture is located here, http://www.linuxtopia.org/online_books/graphics_tools/gimp_advanced_guide/gimp_guide_node74.html.

Original source