如何在 Python 中使用 Pillow 库向 PNG 图像添加轮廓/描边/边框?

Gil*_*tin 4 python image-manipulation border python-imaging-library

我正在尝试使用 Pillow (python-imaging-library) Python 库在我的 .png 图像周围创建轮廓/描边/边框(选择任何颜色和宽度)。您可以在这里看到原始图像和我想要的结果(由手机应用程序创建):

示例图像

您可以在这里下载原始图像的png文件:https://pixabay.com/illustrations/brain-character-organ-smart-eyes-1773885/

我已经用中等尺寸(1280x1138)完成了它,但也许用最小尺寸(640x569)来完成它更好。

我尝试用两种方法解决这个问题。

方法一

第一种方法是创建 Brain.png 图像的全黑图像,将其放大,然后将原始彩色大脑图像粘贴在其上。这是我的代码:

brain_black = Image.open("brain.png") #load brain image
width = brain_black.width #in order not to type a lot
height = brain_black.height #in order not to type a lot
rectangle = Image.new("RGBA", (width, height), "black") #creating a black rectangle in the size of the brain image
brain_black.paste(rectangle, mask=brain_black) #pasting on the brain image the black rectangle, and masking it with the brain picture

#now brain_black is the brain.png image, but all its pixels are black. Let's continue:

brain_black = brain_black.resize((width+180, height+180)) #resizing the brain_black by some factor
brain_regular = Image.open("brain.png") #load the brain image in order to paste later on
brain_black.paste(brain_regular,(90,90), mask=brain_regular) #paste the regular (colored) brain on top of the enlarged black brain (in x=90, y=90, the middle of the black brain)
brain_black.save("brain_method_resize.png") #saving the image
Run Code Online (Sandbox Code Playgroud)

正如您在上面的图片链接中看到的那样,此方法不起作用。它可能适用于简单的几何形状,但不适用于像这样的复杂形状。

方法二

第二种方法是将大脑图像像素数据加载到二维数组中,并循环遍历所有像素。检查每个像素的颜色,并在每个不透明的像素(即rgbA形式中的A(或Alpha)不为0)中绘制一个黑色像素,在该像素的上、下、右、左、主对角线下,主对角线向上,次对角线 (/) 向下,次对角线 (/) 向上。然后在上面的第二个像素、下面的第二个像素等中绘制一个像素。这是通过“for 循环”完成的,其中重复次数是所需的笔划宽度(在本例中为 30)。这是我的代码:

brain=Image.open("brain.png") #load brain image
background=Image.new("RGBA", (brain.size[0]+400, brain.size[1]+400), (0, 0, 0, 0)) #crate a background transparent image to create the stroke in it
background.paste(brain, (200,200), brain) #paste the brain image in the middle of the background
pixelsBrain = brain.load() #load the pixels array of brain
pixelsBack=background.load() #load the pixels array of background

for i in range(brain.size[0]):
    for j in range(brain.size[1]):
        r, c = i+200, j+200 #height and width offset 
        if(pixelsBrain[i,j][3]!=0): #checking if the opacity is not 0, if the alpha is not 0.
            for k in range(30): #the loop
                pixelsBack[r, c + k] = (0, 0, 0, 255)
                pixelsBack[r, c - k] = (0, 0, 0, 255)
                pixelsBack[r + k, c] = (0, 0, 0, 255)
                pixelsBack[r - k, c] = (0, 0, 0, 255)
                pixelsBack[r + k, c + k] = (0, 0, 0, 255)
                pixelsBack[r - k, c - k] = (0, 0, 0, 255)
                pixelsBack[r + k, c - k] =(0, 0, 0, 255)
                pixelsBack[r - k, c + k] = (0, 0, 0, 255)

background.paste(brain, (200,200), brain) #pasting the colored brain onto the background, because the loop "destroyed" the picture.

background.save("brain_method_loop.png")
Run Code Online (Sandbox Code Playgroud)

这个方法确实有效,但是非常耗时(一张图片和30个像素的笔划大约需要30秒)。我想对很多图片进行此操作,所以这种方法不适合我。

有没有一种更简单、更好的方法来使用 Python Pillow 库达到我想要的结果。我该怎么做?另外,我怎样才能紧固我的循环代码(我了解一些关于 Numpy 和 OpenCV 的知识,哪个更适合这个目的?)

我知道如果手机应用程序可以在几毫秒内完成,Python 也可以,但我没有找到任何方法来做到这一点。

谢谢。

Seo*_* So 7

我使用 OpenCV 尝试了一些与 Photoshop 描边效果类似的解决方案(它并不完美,我仍在寻找更好的解决方案)

该算法基于欧氏距离变换。我也尝试过椭圆核结构的膨胀算法,它和photoshop有点不同,有一些信息说距离变换是photoshop使用的方式。

def stroke(origin_image, threshold, stroke_size, colors):
    img = np.array(origin_image)
    h, w, _ = img.shape
    padding = stroke_size + 50
    alpha = img[:,:,3]
    rgb_img = img[:,:,0:3]
    bigger_img = cv2.copyMakeBorder(rgb_img, padding, padding, padding, padding, 
                                        cv2.BORDER_CONSTANT, value=(0, 0, 0, 0))
    alpha = cv2.copyMakeBorder(alpha, padding, padding, padding, padding, cv2.BORDER_CONSTANT, value=0)
    bigger_img = cv2.merge((bigger_img, alpha))
    h, w, _ = bigger_img.shape
    
    _, alpha_without_shadow = cv2.threshold(alpha, threshold, 255, cv2.THRESH_BINARY)  # threshold=0 in photoshop
    alpha_without_shadow = 255 - alpha_without_shadow
    dist = cv2.distanceTransform(alpha_without_shadow, cv2.DIST_L2, cv2.DIST_MASK_3)  # dist l1 : L1 , dist l2 : l2
    stroked = change_matrix(dist, stroke_size)
    stroke_alpha = (stroked * 255).astype(np.uint8)

    stroke_b = np.full((h, w), colors[0][2], np.uint8)
    stroke_g = np.full((h, w), colors[0][1], np.uint8)
    stroke_r = np.full((h, w), colors[0][0], np.uint8)

    stroke = cv2.merge((stroke_b, stroke_g, stroke_r, stroke_alpha))
    stroke = cv2pil(stroke)
    bigger_img = cv2pil(bigger_img)
    result = Image.alpha_composite(stroke, bigger_img)
    return result

def change_matrix(input_mat, stroke_size):
    stroke_size = stroke_size - 1
    mat = np.ones(input_mat.shape)
    check_size = stroke_size + 1.0
    mat[input_mat > check_size] = 0
    border = (input_mat > stroke_size) & (input_mat <= check_size)
    mat[border] = 1.0 - (input_mat[border] - stroke_size)
    return mat

def cv2pil(cv_img):
    cv_img = cv2.cvtColor(cv_img, cv2.COLOR_BGRA2RGBA)
    pil_img = Image.fromarray(cv_img.astype("uint8"))
    return pil_img
    
    
output = stroke(test_image, threshold=0, stroke_size=10, colors=((0,0,0),))
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述