这个Python图像模糊函数有什么问题?

5

编辑: 感谢Howard的帮助,我已经纠正了代码,并且现在看起来它可以工作了。

编辑2: 我已经更新了代码,以包括最初想要的垂直模糊。各种设置下的结果样本输出: 模糊比较图像.jpg

另一个关于模糊操作的参考(Java): 初学者的模糊处理


原始帖子:

我正在尝试学习基本的图像处理,并在python中复制这个简单的模糊方法 (在“重用结果”下的第二个函数BlurHorizontal)。我知道PIL中已经有了模糊函数,但是我想自己尝试基本的像素操作。

这个函数应该接收一个源图像,然后根据一定的半径平均RGB像素值,并将处理后的图像写入一个新文件。我的问题是,我得到了很多像素完全错误的平均值(例如,在某些区域,亮绿色线条而不是红色)。

使用模糊半径为2时,平均方法会将中心在输入像素上的5个像素的RGB值相加。它使用“滑动窗口”来保持运行总数,减去传出像素(左侧)并添加新的传入像素(窗口右侧)。在这里解释模糊方法

样本: 模糊测试图像输出.jpg

有什么想法我哪里做错了吗?我不确定为什么图像的某些部分可以清晰地模糊,而其他区域则充满了与周围区域完全无关的颜色。

感谢您的帮助。

修复后的可工作代码 (感谢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')

2
你能发布一些样例输入/输出吗? - Blender
1
每当我看到 a-b-c 时,我总是感到担忧。我从未记住过任何语言中运算符的结合性,无法确定它是被解释为 a-(b-c) 还是 (a-b)-c - sarnold
1
在我使用过的每种编程语言中,加法、减法、乘法和除法都是从左到右结合的,就像数学中一样。指数运算是唯一常常从右到左结合的运算符。 - David Z
@nkint:不,它不快。我对优化不是很了解。我知道我可以将其压缩成更少的行,但我试图保持清晰易懂。根据我的参考链接,下一步是使用转置函数或按相反顺序编写处理后的像素,并重新使用一个水平函数而不是使用单独的垂直函数。无论如何,我现在要尝试进入矩阵(3x3框)和高斯加权模糊计算(而不是未加权的平均值)。 - moski
@Tim Pietzcker,你试过了吗?10-(5-1) -> 6. (10-5)-1 -> 4. 来吧,试试看。:) 我_曾经_见过一些语言以两种方式解析a-b-c,所以我总是更喜欢在分组周围加上括号,以防我忘记特定语言的结合规则。 - sarnold
显示剩余3条评论
2个回答

2

我猜您在这一行遇到了问题

for rads in range(-radius, radius):

仅运行到半径-1(范围不包括最后一个)。将第二个范围参数加1。

更新:该行中还有另一个小问题。

if (co-radius-1) > 0:

应该是

if (co-radius-1) >= 0:

感谢所有的评论。我尝试了Howard的建议并更改了以下行:`for rads in range(-radius, radius+1)`:但我仍然必须在某个地方有另一个问题。这是一张示例图像,显示了之前和之后的图像。第二张图像是由我的原始代码处理的。第三张图像是在进行Howard的更改后处理的。模糊测试图像输出.jpg - moski
@moski 还有一个小问题:如果 (co-radius-1) > 0,则应该改为 if (co-radius-1) >= 0。 - Howard
非常感谢!我认为这两个更改已经修复了我的错误。我往往会错过所有小细节,比如> vs. >=以及+1,-1来解决从0开始计数和从1开始计数的问题。感谢你的第二双眼睛。我将尝试一些不同的图像,然后发布更新。模糊修复输出.png - moski
再次感谢。我测试了多张图片,它们都按预期处理。为了完成完整的模糊效果,我添加了一个垂直窗口模糊计算。水平方向和水平+垂直方向的样本图像,具有不同的半径和多个通道:Blur FIXED comparison images.jpg - moski
@moski,“我往往会错过所有的小细节”,你会惊讶于有多少安全漏洞就是那个微小的问题... :) 好工作 Howard,发现得好。 - sarnold

0

我稍微修改/重构了你的代码,并想分享一下。我需要做一个自定义模糊效果,它必须:1)适用于数据数组,2)只能水平包裹而不是垂直包裹。如TODO注释所述,我考虑进一步重构,以便可以进行部分像素混合(即0.5)。希望这对某些人有所帮助:

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

如果你已经有一个RGBA像素数组的图像,那么可以运行以下代码:

image_data = blur_image(image_data, height=height, width=width, radius=2)

im = Image.new('RGB', (width, height))
im.putdata(image_data)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接