python 如何在图像周围添加圆形边框?

brgchamk  于 12个月前  发布在  Python
关注(0)|答案(5)|浏览(163)

我有一个矩形图像,我想圆它的角落,然后添加一个黑色的边界(所以边界也是圆的)。
有没有一个简单的方法来实现它?
这将是所需的输出:


的数据
Similar unanswered question

nxowjjhe

nxowjjhe1#

在我的第一个答案的评论中与Mark进行了一些讨论后,我决定使用OpenCV和NumPy来制作另一个解决方案,它能够轻松地将一些真实的图像(例如照片)提供给该方法,并获得包括圆角边框和边框外透明度的图像!

import cv2
import numpy as np

def rect_with_rounded_corners(image, r, t, c):
    """
    :param image: image as NumPy array
    :param r: radius of rounded corners
    :param t: thickness of border
    :param c: color of border
    :return: new image as NumPy array with rounded corners
    """

    c += (255, )

    h, w = image.shape[:2]

    # Create new image (three-channel hardcoded here...)
    new_image = np.ones((h+2*t, w+2*t, 4), np.uint8) * 255
    new_image[:, :, 3] = 0

    # Draw four rounded corners
    new_image = cv2.ellipse(new_image, (int(r+t/2), int(r+t/2)), (r, r), 180, 0, 90, c, t)
    new_image = cv2.ellipse(new_image, (int(w-r+3*t/2-1), int(r+t/2)), (r, r), 270, 0, 90, c, t)
    new_image = cv2.ellipse(new_image, (int(r+t/2), int(h-r+3*t/2-1)), (r, r), 90, 0, 90, c, t)
    new_image = cv2.ellipse(new_image, (int(w-r+3*t/2-1), int(h-r+3*t/2-1)), (r, r), 0, 0, 90, c, t)

    # Draw four edges
    new_image = cv2.line(new_image, (int(r+t/2), int(t/2)), (int(w-r+3*t/2-1), int(t/2)), c, t)
    new_image = cv2.line(new_image, (int(t/2), int(r+t/2)), (int(t/2), int(h-r+3*t/2)), c, t)
    new_image = cv2.line(new_image, (int(r+t/2), int(h+3*t/2)), (int(w-r+3*t/2-1), int(h+3*t/2)), c, t)
    new_image = cv2.line(new_image, (int(w+3*t/2), int(r+t/2)), (int(w+3*t/2), int(h-r+3*t/2)), c, t)

    # Generate masks for proper blending
    mask = new_image[:, :, 3].copy()
    mask = cv2.floodFill(mask, None, (int(w/2+t), int(h/2+t)), 128)[1]
    mask[mask != 128] = 0
    mask[mask == 128] = 1
    mask = np.stack((mask, mask, mask), axis=2)

    # Blend images
    temp = np.zeros_like(new_image[:, :, :3])
    temp[(t-1):(h+t-1), (t-1):(w+t-1)] = image.copy()
    new_image[:, :, :3] = new_image[:, :, :3] * (1 - mask) + temp * mask

    # Set proper alpha channel in new image
    temp = new_image[:, :, 3].copy()
    new_image[:, :, 3] = cv2.floodFill(temp, None, (int(w/2+t), int(h/2+t)), 255)[1]

    return new_image

img = cv2.imread('path/to/your/image.png')
cv2.imshow('img', img)

new_img = rect_with_rounded_corners(img, 50, 20, (0, 0, 0))
cv2.imshow('new_img', new_img)

cv2.waitKey(0)
cv2.destroyAllWindows()

字符串
这和我在另一个答案中使用的概念是一样的,只是在正确的透明度问题上多写了一些代码。
一些示例性输入:
x1c 0d1x的数据
相应的输出:



另一个输入和参数集:


new_img = rect_with_rounded_corners(img, 20, 10, (0, 0, 128))


输出量:

希望也有帮助!

----------------------------------------
System information
----------------------------------------
Platform:    Windows-10-10.0.16299-SP0
Python:      3.8.1
NumPy:       1.18.1
OpenCV:      4.2.0
----------------------------------------

qeeaahzv

qeeaahzv2#

我喜欢用SVG画圆角矩形,这是一种改变--不仅仅是因为有人认为我总是使用ImageMagick ;-)

#!/usr/bin/env python3

from PIL import ImageOps, Image
from cairosvg import svg2png
from io import BytesIO

def frame(im, thickness=5):
    # Get input image width and height, and calculate output width and height
    iw, ih = im.size
    ow, oh = iw+2*thickness, ih+2*thickness

    # Draw outer black rounded rect into memory as PNG
    outer = f'<svg width="{ow}" height="{oh}" style="background-color:none"><rect rx="20" ry="20" width="{ow}" height="{oh}" fill="black"/></svg>'
    png   = svg2png(bytestring=outer)
    outer = Image.open(BytesIO(png))

    # Draw inner white rounded rect, offset by thickness into memory as PNG
    inner = f'<svg width="{ow}" height="{oh}"><rect x="{thickness}" y="{thickness}" rx="20" ry="20" width="{iw}" height="{ih}" fill="white"/></svg>'
    png   = svg2png(bytestring=inner)
    inner = Image.open(BytesIO(png)).convert('L')

    # Expand original canvas with black to match output size
    expanded = ImageOps.expand(im, border=thickness, fill=(0,0,0)).convert('RGB')

    # Paste expanded image onto outer black border using inner white rectangle as mask
    outer.paste(expanded, None, inner)
    return outer

# Open image, frame it and save
im = Image.open('monsters.jpg')
result = frame(im, thickness=10)
result.save('result.png')

字符串

输出图片

x1c 0d1x的数据

输入图片



您可以使用rxry来更改角的半径。
这里是outerinnerexpanded-正如你所看到的,它们都是相同的大小,以便在彼此之上进行组合。



其他想法:

  • 你也可以通过在一个黑盒子里画一个白色矩形,然后在它上面运行一个中值滤波器,或者一些形态学侵 eclipse ,来创建一个圆角。如果你过滤这个:

使用15 x15的中值滤波器,你会得到:

如果有人想要一个ImageMagick解决方案:

#!/bin/bash

# Get width and height of input image
read iw ih < <(identify -format "%w %h" monsters.jpg)

# Calculate size of output image, assumes thickness=10
((ow=iw+20))
((oh=ih+20))

magick -size ${ow}x${oh} xc:none  -fill black -draw "roundrectangle 0,0 $ow,$oh 20,20" \
    \( -size ${iw}x${ih} xc:black -fill white -draw "roundrectangle 0,0,$iw,$ih 20,20" monsters.jpg -compose darken -composite \) \
       -gravity center -compose over -composite result.png

Keywords:Python,图像处理,圆角,圆角,边框,SVG,cairo,cairosvg,SVG to PNG,SVG as PNG,SVG to PIL,PIL,Pillow。

hlswsv35

hlswsv353#

当然,Mark会使用ImageMagick提供一个很好的解决方案。但是,因为你的问题是用Pillow标记的,其他人可能也在寻找解决方案,这里是我的手动实现,因为我怀疑,有一个现成的内置方法:

from matplotlib import pyplot as plt        # Just for visualization
from PIL import Image, ImageDraw

def rect_with_rounded_corners(image, r, t, c):
    """
    :param image: PIL image, assumption: uni color filled rectangle
    :param r: radius of rounded corners
    :param t: thickness of border
    :param c: color of border
    :return: new PIL image of rectangle with rounded corners
    """

    # Some method to extract the main color of the rectangle needed here ...
    mc = img.getpixel((image.width/2, image.height/2))

    # Create new image
    new_image = Image.new(image.mode, (image.width + 2*t, image.height + 2*t), (255, 255, 255))
    draw = ImageDraw.Draw(new_image)

    # Draw four rounded corners
    draw.arc([(0, 0), (2*r-1, 2*r-1)], 180, 270, c, t)
    draw.arc([(image.width-2*r+2*t, 0), (image.width+2*t, 2*r-1)], 270, 0, c, t)
    draw.arc([(image.width-2*r+2*t, image.height-2*r+2*t), (image.width+2*t, image.height+2*t)], 0, 90, c, t)
    draw.arc([(0, image.height-2*r+2*t), (2*r-1, image.height+2*t)], 90, 180, c, t)

    # Draw four edges
    draw.line([(r-1, t/2-1), (image.width-r+2*t, t/2-1)], c, t)
    draw.line([(t/2-1, r-1), (t/2-1, image.height-r+2*t)], c, t)
    draw.line([(image.width+1.5*t, r-1), (image.width+1.5*t, image.height-r+2*t)], c, t)
    draw.line([(r-1, image.height+1.5*t), (image.width-r+2*t, image.height+1.5*t)], c, t)

    # Fill rectangle with main color
    ImageDraw.floodfill(new_image, (image.width/2+t, image.height/2+t), mc)

    return new_image

img = Image.new('RGB', (640, 480), (255, 128, 255))
plt.figure(1)
plt.imshow(img)

new_img = rect_with_rounded_corners(img, 100, 20, (0, 0, 0))
plt.figure(2)
plt.imshow(new_img)

plt.show()

字符串
基本上,它是计算和手动绘制四个弧,四个边缘与所需的厚度和颜色的边界,然后泛色填充矩形与初始矩形的颜色。把它放在一些方法,并在需要时重用它,所以没有混乱的主要代码。
对于指定的图像和参数集,我们得到输出(Matplotlib图在这里):
x1c 0d1x的数据
对于另一个图像和参数集,

img = Image.new('RGB', (400, 300), (0, 64, 255))
plt.figure(1)
plt.imshow(img)

new_img = rect_with_rounded_corners(img, 25, 10, (255, 0, 0))
plt.figure(2)
plt.imshow(new_img)


我们得到,例如:



希望能帮上忙!

----------------------------------------
System information
----------------------------------------
Platform:    Windows-10-10.0.16299-SP0
Python:      3.8.1
Matplotlib:  3.2.0rc3
Pillow:      7.0.0
----------------------------------------

8ljdwjyq

8ljdwjyq4#

这里是另一种使用Python/OpenCV的方法。然而,在这种方法中,边框将在输入图像的边界内。

  • 读取输入
  • 创建输入大小的白色图像
  • 用黑色填充白色图像,使其周围达到所需的边框厚度
  • 对填充图像应用高斯模糊
  • 对模糊图像进行阈值处理,形成二值图像
  • 腐 eclipse 阈值图像以形成第二二个二值图像
  • 得到两个二值图像之间的差以形成边界形状掩模
  • 根据厚度修剪边框蒙版,使其恢复到输入图像的大小
  • 创建与输入大小相同的彩色图像
  • 使用蒙版将输入和彩色图像合并组合
  • 将第一个阈值化图像放入组合图像的alpha通道中,使外部透明
  • 保存结果

输入:
x1c 0d1x的数据

import cv2
import numpy as np

# set thickness, rounding and color of border
t = 21
r = 21
c = (0,0,255)

# read image
img = cv2.imread("bear.png")
hh, ww = img.shape[0:2]

# create white image of size of input
white = np.full_like(img, (255,255,255))

# add black border of thickness
border = cv2.copyMakeBorder(white, t, t, t, t, borderType=cv2.BORDER_CONSTANT, value=(0,0,0))

# blur image by rounding amount as sigma
blur = cv2.GaussianBlur(border, (0,0), r, r)

# threshold blurred image
thresh1 = cv2.threshold(blur, 128, 255, cv2.THRESH_BINARY)[1]

# create thesh2 by eroding thresh1 by 2*t
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (2*t,2*t))
thresh2 = cv2.morphologyEx(thresh1, cv2.MORPH_ERODE, kernel, iterations=1)

# subtract the two thresholded images to make a border mask
mask = thresh1 - thresh2

# shave border mask by t
mask = mask[t:hh+t,t:ww+t]

# create colored image the same size as input
color = np.full_like(img, c)

# combine input and color with mask
result = cv2.bitwise_and(color, mask) + cv2.bitwise_and(img, 255-mask)

# add thresh1 as alpha channel
thresh1 = thresh1[t:hh+t,t:ww+t][:,:,0]
result = np.dstack([result,thresh1])

# write 
cv2.imwrite("bear_thresh1.png", thresh1)
cv2.imwrite("bear_thresh2.png", thresh2)
cv2.imwrite("bear_mask.png", mask)
cv2.imwrite("bear_red_border.png", result)

# display it
cv2.imshow("IMAGE", img)
cv2.imshow("BORDER", border)
cv2.imshow("BLUR", blur)
cv2.imshow("THRESHOLD1", thresh1)
cv2.imshow("THRESHOLD2", thresh2)
cv2.imshow("MASK", mask)
cv2.imshow("RESULT", result)
cv2.waitKey(0)

字符串
阈值1图像:



阈值2图像:



边框蒙版图像:

结果图像:

添加

这里是对上面的修正,允许更多的厚度和半径值。示例使用厚度21和半径81。

import cv2
import numpy as np
import skimage.exposure

# set thickness, rounding and color of border
t = 21
r = 81
c = (0,0,255)

# read image
img = cv2.imread("bear.png")
hh, ww = img.shape[0:2]

# create white image of size of input
white = np.full_like(img, (255,255,255))

# add black border of thickness r
border = cv2.copyMakeBorder(white, r,r,r,r, borderType=cv2.BORDER_CONSTANT, value=(0,0,0)).astype(np.float64)

# blur image by rounding amount as sigma
blur = cv2.GaussianBlur(border, (0,0), r, r)

# threshold blurred image
thresh1 = cv2.threshold(blur, 128, 255, cv2.THRESH_BINARY)[1]

# create thesh2 by eroding thresh1 by 2*t
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (2*t,2*t))
thresh2 = cv2.morphologyEx(thresh1, cv2.MORPH_ERODE, kernel, iterations=1)

# subtract the two thresholded images to make a border mask
mask = thresh1 - thresh2

# antialias
mask = mask.astype(np.float64)
smooth = cv2.GaussianBlur(mask, (0,0), sigmaX=3, sigmaY=3, borderType = cv2.BORDER_DEFAULT)
mask = skimage.exposure.rescale_intensity(smooth, in_range=(96,160), out_range=(0,255))

# shave border mask by r
mask = mask[r:hh+r,r:ww+r]

# create colored image the same size as input
color = np.full_like(img, c)

# combine input and color with mask
#result = cv2.bitwise_and(color, mask) + cv2.bitwise_and(img, 255-mask)
result = ((color*mask + img*(255-mask))/255).clip(0,255).astype(np.uint8)

# add thresh1 as alpha channel
thresh1 = thresh1[r:hh+r,r:ww+r][:,:,0]
result = np.dstack([result,thresh1])

# write 
cv2.imwrite("bear2_thresh1.png", thresh1)
cv2.imwrite("bear2_thresh2.png", thresh2)
cv2.imwrite("bear2_mask.png", mask)
cv2.imwrite("bear2_red_border.png", result)

# display it
cv2.imshow("IMAGE", img)
cv2.imshow("BORDER", border)
cv2.imshow("BLUR", blur)
cv2.imshow("THRESHOLD1", thresh1)
cv2.imshow("THRESHOLD2", thresh2)
cv2.imshow("MASK", mask)
cv2.imshow("RESULT", result)
cv2.waitKey(0)


测试结果:

csga3l58

csga3l585#

这里是一个简单的实现只是枕头:

from PIL import Image, ImageDraw

def add_round_border(
    image, border_color=(232, 232, 232), border_radius=30, border_width=3
):  
    image = image.convert("RGBA")
    # Create an out mask and an in mask
    mask = Image.new("L", image.size, 0)
    draw = ImageDraw.Draw(mask)
    draw.rounded_rectangle(
        [0, 0, image.size[0], image.size[1]], radius=border_radius, fill=255
    )
    mask_in = Image.new("L", image.size, 0)
    draw = ImageDraw.Draw(mask_in)
    draw.rounded_rectangle(
        [
            border_width,
            border_width,
            image.size[0] - border_width,
            image.size[1] - border_width,
        ],
        radius=border_radius - border_width,
        fill=255,
    )

    border_image = Image.new("RGBA", image.size, color=border_color)
    new_image = Image.new("RGBA", image.size, color=0)
    # Add the border by pasting the border images onto the new image
    new_image.paste(border_image, mask=mask)
    new_image.paste(image, mask=mask_in)
    return new_image

# Driver Code
image = Image.open("YOUR_IMAGE_PATH")
image_with_border = add_round_border(image, border_color="red", border_radius=30, border_width=15)
image.save("test.png")
image_with_border.save("test_with_border.png")

字符串
之前:https://i.stack.imgur.com/Cm92N.jpg
后:https://i.stack.imgur.com/3HIuK.jpg

相关问题