Как добавить круглую рамку вокруг изображения? - PullRequest
0 голосов
/ 24 февраля 2020

У меня есть изображение прямоугольника, и я хотел бы скруглить его углы, а затем добавить к нему черную рамку (чтобы она также была круглой).

Есть ли простой способ добиться этого?

Это будет желаемый результат:

enter image description here

Аналогичный вопрос без ответа

Ответы [ 4 ]

3 голосов
/ 25 февраля 2020

После некоторого обсуждения с Марком в комментариях к моему первому ответу я решил сделать другое решение, используя 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()

Это та же концепция, что и в моем другом ответе, с дополнительным кодом правильной прозрачности.

Некоторые примерные данные :

Input #1

Соответствующий вывод:

Output #1

Другой вход и набор параметров:

Input #2

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

Выход:

Output #2

Надеюсь, что это также помогает!

----------------------------------------
System information
----------------------------------------
Platform:    Windows-10-10.0.16299-SP0
Python:      3.8.1
NumPy:       1.18.1
OpenCV:      4.2.0
----------------------------------------
3 голосов
/ 25 февраля 2020

Мне нравилось рисовать закругленные прямоугольники с помощью 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')

Выходное изображение

enter image description here

Входное изображение

enter image description here

Вы можете играть с rx и ry для изменения радиуса углов.

Вот outer, inner и expanded - как вы можете видеть, они все одинакового размера друг для друга для простоты составление друг на друга.

enter image description here

Другие идеи:

  • Вы также можете создать закругленный угол, нарисовав белый прямоугольник в черном ящике и над ним проходит медианный фильтр или какая-то морфологическая эрозия. Если вы отфильтруете это:

enter image description here

с медианным фильтром 15x15, вы получите это:

enter image description here


На всякий случай, если кому-то понадобится решение 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

enter image description here

Ключевые слова : Python, обработка изображений, закругленные углы, закругленные углы, рамка, SVG, Каир, Cairosvg, SVG в PNG, SVG в PNG, SVG в PIL, PIL, Pillow.

1 голос
/ 26 февраля 2020

Вот еще один подход, использующий Python / OpenCV. Однако при таком подходе граница будет находиться внутри границ входного изображения.

  • Чтение ввода
  • Создание белого изображения с размером ввода
  • Дополните белое изображение черным вокруг желаемой толщины границы
  • Примените размытие по Гауссу к дополненному изображению
  • Порог размытого изображения для формирования двоичного изображения
  • Эрозия пороговое изображение для формирования второго двоичного изображения
  • Получите разницу между двумя двоичными изображениями, чтобы сформировать маску в форме границы
  • Побрейте маску границы по толщине, чтобы вернуть ее к размеру входное изображение
  • Создание цветного изображения с размером входного сигнала
  • Объединение входного и цветного изображения с помощью маски
  • Поместите первое пороговое изображение в альфа-канал объединенное изображение, чтобы сделать снаружи прозрачным
  • Сохранить результаты

Ввод:

enter image description here

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 има ge:

enter image description here

Порог 2 изображения:

enter image description here

Граничная маска изображение:

enter image description here

Результат изображение:

enter image description here

1 голос
/ 25 февраля 2020

Конечно, Марк предоставит необычное решение, используя 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 здесь):

Orange

Для другого изображения и параметра set

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)

получаем, например:

Blue

Надеюсь, что поможет!

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