9

EDIT: Code is working now, thanks to Mark and zephyr. zephyr also has two alternate working solutions below.

I want to divide blend two images with PIL. I found ImageChops.multiply(image1, image2) but I couldn't find a similar divide(image, image2) function.

Divide Blend Mode Explained (I used the first two images here as my test sources.)

Is there a built-in divide blend function that I missed (PIL or otherwise)?

My test code below runs and is getting close to what I'm looking for. The resulting image output is similar to the divide blend example image here: Divide Blend Mode Explained.

Is there a more efficient way to do this divide blend operation (less steps and faster)? At first, I tried using lambda functions in Image.eval and ImageMath.eval to check for black pixels and flip them to white during the division process, but I couldn't get either to produce the correct result.

EDIT: Fixed code and shortened thanks to Mark and zephyr. The resulting image output matches the output from zephyr's numpy and scipy solutions below.

# PIL Divide Blend test

import Image, os, ImageMath

imgA = Image.open('01background.jpg')
imgA.load()
imgB = Image.open('02testgray.jpg')
imgB.load()

# split RGB images into 3 channels
rA, gA, bA = imgA.split()
rB, gB, bB = imgB.split()

# divide each channel (image1/image2)
rTmp = ImageMath.eval("int(a/((float(b)+1)/256))", a=rA, b=rB).convert('L')
gTmp = ImageMath.eval("int(a/((float(b)+1)/256))", a=gA, b=gB).convert('L')
bTmp = ImageMath.eval("int(a/((float(b)+1)/256))", a=bA, b=bB).convert('L')

# merge channels into RGB image
imgOut = Image.merge("RGB", (rTmp, gTmp, bTmp))

imgOut.save('PILdiv0.png', 'PNG')

os.system('start PILdiv0.png')
moski
  • 279
  • 1
  • 4
  • 10

3 Answers3

4

You are asking:

Is there a more efficient way to do this divide blend operation (less steps and faster)?

You could also use the python package blend modes. It is written with vectorized Numpy math and generally fast. Install it via pip install blend_modes. I have written the commands in a more verbose way to improve readability, it would be shorter to chain them. Use blend_modes like this to divide your images:

from PIL import Image
import numpy
import os
from blend_modes import blend_modes

# Load images
imgA = Image.open('01background.jpg')
imgA = numpy.array(imgA)
# append alpha channel
imgA = numpy.dstack((imgA, numpy.ones((imgA.shape[0], imgA.shape[1], 1))*255))
imgA = imgA.astype(float)

imgB = Image.open('02testgray.jpg')
imgB = numpy.array(imgB)
# append alpha channel
imgB = numpy.dstack((imgB, numpy.ones((imgB.shape[0], imgB.shape[1], 1))*255))
imgB = imgB.astype(float)

# Divide images
imgOut = blend_modes.divide(imgA, imgB, 1.0)

# Save images
imgOut = numpy.uint8(imgOut)
imgOut = Image.fromarray(imgOut)
imgOut.save('PILdiv0.png', 'PNG')

os.system('start PILdiv0.png')

Be aware that for this to work, both images need to have the same dimensions, e.g. imgA.shape == (240,320,3) and imgB.shape == (240,320,3).

flrs
  • 41
  • 3
3

There is a mathematical definition for the divide function here: http://www.linuxtopia.org/online_books/graphics_tools/gimp_advanced_guide/gimp_guide_node55_002.html

Here's an implementation with scipy/matplotlib:

import numpy as np
import scipy.misc as mpl

a = mpl.imread('01background.jpg')
b = mpl.imread('02testgray.jpg')

c = a/((b.astype('float')+1)/256)
d = c*(c < 255)+255*np.ones(np.shape(c))*(c > 255)

e = d.astype('uint8')

mpl.imshow(e)
mpl.imsave('output.png', e)

If you don't want to use matplotlib, you can do it like this (I assume you have numpy):

imgA = Image.open('01background.jpg')
imgA.load()
imgB = Image.open('02testgray.jpg')
imgB.load()

a = asarray(imgA)
b = asarray(imgB)
c = a/((b.astype('float')+1)/256)
d = c*(c &lt 255)+255*ones(shape(c))*(c &gt 255)
e = d.astype('uint8')

imgOut = Image.fromarray(e)
imgOut.save('PILdiv0.png', 'PNG')

jfs
  • 399,953
  • 195
  • 994
  • 1,670
so12311
  • 4,179
  • 1
  • 29
  • 37
  • Thanks for your solution. That's much more straightforward. I'll take a look at pylab and matplotlib. I guess there's nothing that simple in PIL? That link is also very helpful for understanding other blend modes. – moski Apr 09 '11 at 15:24
  • sorry, I'm not very familiar with PIL.. I've only ever used it for loading/saving files. I imagine there probably is a way to do it, but I find it easier to do math stuff in numpy. – so12311 Apr 09 '11 at 15:38
  • Thanks again. I do have numpy and scipy already, so I'll try your numpy method too. (Oh, I see I do have matplotlib too.) I was trying to convert your earlier post back to my PIL function and it wasn't quite working yet. I see you've updated things in the meantime, so I'll try again with those changes. – moski Apr 09 '11 at 16:22
  • Thanks. I ran both of your solutions and incorporated changes into my original PIL version (trimming out a lot). All three versions match bit-for-bit on my test images. I don't quite follow your `d=` line, but I'm guessing it works like the min(255,c) in the GIMP guide equation. I couldn't squeeze that into my `ImageMath.eval`, but the output still matches yours. `ImageMath.eval` may have the clipping to 255 built in. – moski Apr 09 '11 at 17:28
1

The problem you're having is when you have a zero in image B - it causes a divide by zero. If you convert all of those values to one instead I think you'll get the desired result. That will eliminate the need to check for zeros and fix them in the result.

Mark Ransom
  • 299,747
  • 42
  • 398
  • 622