views:

536

answers:

2

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.

Does anyone know how to do this with PIL? At the very least I want to adjust the "depth" of the embossed image. Thanks.

Update: I tried things suggested by bpowah ( modifying the filterargssuch as scale, offset and the matrix .) but 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

alt text

+5  A: 

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

alt text

after

alt text

bpowah
Thank you for answering! Yes, I tried tweaking params in the 3 X 3 matrix. But I am not getting the desired effect ... i.e. I want to "increase the depth effect" . Could you please explain what these matrix elements are .. I couldn't find the explanation... thanks once again!
cppb
wow! this is an excellent explanation.
cppb
+2  A: 

To increase the depth of an emboss filter, increase the radius of the filter's mask. Low depth:

h = [[1, 0, 0]
     [0, 0, 0]
     [0, 0, -1]]

versus high depth:

h = [[1, 0, 0, 0, 0, 0, 0]
     [0, 0, 0, 0, 0, 0, 0]
     [0, 0, 0, 0, 0, 0, 0]
     [0, 0, 0, 0, 0, 0, 0]
     [0, 0, 0, 0, 0, 0, 0]
     [0, 0, 0, 0, 0, 0, 0]
     [0, 0, 0, 0, 0, 0, -1]]

To change the azimuth, place the nonzero coefficients at a different angle:

h = [[0, 0, 1]
     [0, 0, 0]
     [-1, 0, 0]]

I'm not really sure about elevation. You may need to change the nonzero coefficient values? I just know it needs to be a high-pass filter.

In any case, to compute and display the image using a Scipy solution:

import scipy.misc, scipy.signal
im = scipy.misc.imread(filename)
im_out = scipy.signal.convolve2d(im, h, 'same')
scipy.misc.imshow(im_out)

Hope this helps.

EDIT: Okay, as bpowah hinted with PIL, you can adjust filter parameters, or even define a completely new kernel. The scale and offset parameters have nothing to do with what you are looking for. The size of the filter is most important for adjusting depth.

Upon further investigation, PIL does not let you change the filter size beyond 5x5. Seems strange. Therefore, you will not get as dramatic a change in depth as you probably expect.

For total control, you may want to try the Scipy solution I and bpowah mentioned earlier. Change the filter size to something ridiculous, like 21x21, and see if it makes the type of difference you want.

Steve
yes, PIL does not let you specify a filter size beyond 5x5. This post was useful (and as both you and bpowah explained, I think numpy/scipy is a good solution. )
cppb