如何在 Python 中使用 Pillow 库向 PNG 图像添加轮廓/描边/边框? [英] How Can I Add an Outline/Stroke/Border to a PNG Image with Pillow Library in Python?

查看:238
本文介绍了如何在 Python 中使用 Pillow 库向 PNG 图像添加轮廓/描边/边框?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I am trying to use the Pillow (python-imaging-library) Python library in order to create an outline/stroke/border (with any color and width chosen) around my .png image. You can see here the original image and my wanted result (create by a phone app): https://i.stack.imgur.com/4x4qh.png

You can download the png file of the original image here: https://pixabay.com/illustrations/brain-character-organ-smart-eyes-1773885/

I have done it in the medium size(1280x1138) but maybe it is better to do it with the smallest size (640x569).

I tried to solve the problem with two methods.

METHOD ONE

The first method is to create a fully blacked image of the brain.png image, enlarge it, and paste the original colored brain image on top of it. Here is my code:

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

This method doesn't work, as you can see in the image link above. It might have worked for simple geometric shapes, but not for a complicated shape like this.

METHOD TWO

The second method is to load the brain image pixels data into a 2-dimensional array, and loop over all of the pixels. Check the color of every pixel, and in every pixel which is not transparent (means A(or Alpha) is not 0 in the rgbA form) to draw a black pixel in the pixel above, below, right, left, main diagonal down, main diagonal up, secondary diagonal (/) down and secondary diagonal (/) up. Then to draw a pixel in the second pixel above, the second pixel below and etc. this was done with a "for loop" where the number of repetitions is the wanted stroke width (in this example is 30). Here is my code:

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")

This method did work, but it is very time-consuming (takes about 30 seconds just for one picture and 30 pixels stroke). I want to do it for many pictures so this method is not good for me.

Is there an easier and better way to reach my wanted result using Python Pillow library. How can I do it? And also, how can I fasten my loop code (I understood something about Numpy and OpenCV, which is better for this purpose?)

I know that if a phone app could do it in a matter of milliseconds, also python can, but I didn't find any way to do it.

Thank you.

解决方案

I tried some solution similar with photoshop stroke effect using OpenCV (It is not perfect and I still finding better solution)

This algorithm is based on euclidean distance transform. I also tried dilation algorithm with ellipse kernel structure, it is bit different with photoshop, and there are some information that distance transform is the way that photoshop using.

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),))

这篇关于如何在 Python 中使用 Pillow 库向 PNG 图像添加轮廓/描边/边框?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆