What's wrong with this python image blur function?
EDIT: Thanks to Howard, I've corrected the code here and it seems to be working now.
EDIT2: I've updated the code to include a vertical blur as originally intende开发者_JAVA百科d. Resulting sample output with various settings: Blur comparison images.jpg
Another reference for blur operations (Java): Blurring for Beginners
original post:
I'm trying to learn about basic image processing and duplicate this simple Blur method (the second function BlurHorizontal under "Reusing results") in python. I know there are already blur functions in PIL, but I want to try out the basic pixel operations myself.
This function should take a source image, then average RGB pixel values based on a certain radius and write the processed image to a new file. My problem is that I'm getting a lot of pixels with completely wrong averaged values (for example, bright green lines instead of red in certain areas).
With a blur radius of 2, the averaging method adds up the RGB values for the 5 pixels centered on the input pixel. It uses a "sliding window" to keep a running total, subtracting the outgoing pixel (left side) and adding the new incoming pixel (right side of window). Blur method explained here
Sample: Blur test image output.jpg
Any ideas where I've gone wrong? I'm not sure why some parts of the image blur cleanly while other areas are filled with colors completely unrelated to the surrounding areas.
Thanks for your help.
FIXED WORKING Code (Thanks Howard)
import Image, numpy, ImageFilter
img = Image.open('testimage.jpg')
imgArr = numpy.asarray(img) # readonly
# blur radius in pixels
radius = 2
# blur window length in pixels
windowLen = radius*2+1
# columns (x) image width in pixels
imgWidth = imgArr.shape[1]
# rows (y) image height in pixels
imgHeight = imgArr.shape[0]
#simple box/window blur
def doblur(imgArr):
# create array for processed image based on input image dimensions
imgB = numpy.zeros((imgHeight,imgWidth,3),numpy.uint8)
imgC = numpy.zeros((imgHeight,imgWidth,3),numpy.uint8)
# blur horizontal row by row
for ro in range(imgHeight):
# RGB color values
totalR = 0
totalG = 0
totalB = 0
# calculate blurred value of first pixel in each row
for rads in range(-radius, radius+1):
if (rads) >= 0 and (rads) <= imgWidth-1:
totalR += imgArr[ro,rads][0]/windowLen
totalG += imgArr[ro,rads][1]/windowLen
totalB += imgArr[ro,rads][2]/windowLen
imgB[ro,0] = [totalR,totalG,totalB]
# calculate blurred value of the rest of the row based on
# unweighted average of surrounding pixels within blur radius
# using sliding window totals (add incoming, subtract outgoing pixels)
for co in range(1,imgWidth):
if (co-radius-1) >= 0:
totalR -= imgArr[ro,co-radius-1][0]/windowLen
totalG -= imgArr[ro,co-radius-1][1]/windowLen
totalB -= imgArr[ro,co-radius-1][2]/windowLen
if (co+radius) <= imgWidth-1:
totalR += imgArr[ro,co+radius][0]/windowLen
totalG += imgArr[ro,co+radius][1]/windowLen
totalB += imgArr[ro,co+radius][2]/windowLen
# put average color value into imgB pixel
imgB[ro,co] = [totalR,totalG,totalB]
# blur vertical
for co in range(imgWidth):
totalR = 0
totalG = 0
totalB = 0
for rads in range(-radius, radius+1):
if (rads) >= 0 and (rads) <= imgHeight-1:
totalR += imgB[rads,co][0]/windowLen
totalG += imgB[rads,co][1]/windowLen
totalB += imgB[rads,co][2]/windowLen
imgC[0,co] = [totalR,totalG,totalB]
for ro in range(1,imgHeight):
if (ro-radius-1) >= 0:
totalR -= imgB[ro-radius-1,co][0]/windowLen
totalG -= imgB[ro-radius-1,co][1]/windowLen
totalB -= imgB[ro-radius-1,co][2]/windowLen
if (ro+radius) <= imgHeight-1:
totalR += imgB[ro+radius,co][0]/windowLen
totalG += imgB[ro+radius,co][1]/windowLen
totalB += imgB[ro+radius,co][2]/windowLen
imgC[ro,co] = [totalR,totalG,totalB]
return imgC
# number of times to run blur operation
blurPasses = 3
# temporary image array for multiple passes
imgTmp = imgArr
for k in range(blurPasses):
imgTmp = doblur(imgTmp)
print "pass #",k,"done."
imgOut = Image.fromarray(numpy.uint8(imgTmp))
imgOut.save('testimage-processed.png', 'PNG')
I suppose you have an issue with the line
for rads in range(-radius, radius):
which runs to radius-1 only (range excludes last). Add one to the second range argument.
Update: There is another small isue within the line
if (co-radius-1) > 0:
which should be
if (co-radius-1) >= 0:
I modified/refactored your code just a bit, and thought I'd share. I needed something to do a custom blur that would: 1) work on a data array, and 2) only wrap horizontally and not vertically. As the TODO notes, I'm thinking of further refactoring so it can do partial pixel blends (i.e. 0.5). Hope this helps someone:
def blur_image(image_data, blur_horizontal=True, blur_vertical=True, height=256, width=256, radius=1):
#TODO: Modify to support partial pixel blending
# blur window length in pixels
blur_window = radius*2+1
out_image_data = image_data
# blur horizontal row by row, and wrap around edges
if blur_horizontal:
for row in range(height):
for column in range(0, width):
total_red = 0
total_green = 0
total_blue = 0
for rads in range(-radius, radius+1):
pixel = (row*width) + ((column+rads) % width)
total_red += image_data[pixel][0]/blur_window
total_green += image_data[pixel][1]/blur_window
total_blue += image_data[pixel][2]/blur_window
out_image_data[row*width + column] = (total_red, total_green, total_blue, 255)
image_data = out_image_data
# blur vertical, but no wrapping
if blur_vertical:
for column in range(width):
for row in range(0, height):
total_red = 0
total_green = 0
total_blue = 0
blur_window = 0
for rads in range(-radius, radius+1):
if rads in range(0, height):
blur_window += 1
for rads in range(-radius, radius+1):
row_mod = row+rads
if row_mod in range(0, height):
pixel = (row_mod*width) + column
total_red += image_data[pixel][0]/blur_window
total_green += image_data[pixel][1]/blur_window
total_blue += image_data[pixel][2]/blur_window
out_image_data[row*width + column] = (total_red, total_green, total_blue, 255)
image_data = out_image_data
return image_data
You can use it when you've already got an image that's in an array of RGBA pixels, then run:
image_data = blur_image(image_data, height=height, width=width, radius=2)
im = Image.new('RGB', (width, height))
im.putdata(image_data)
精彩评论