[PYTHON] Implémentez des modes de dessin tels que PhotoShop à grande vitesse avec PIL / Pillow

Implémentez des modes de dessin tels que PhotoShop en utilisant uniquement PIL / Pillow.

La façon la plus simple de trouver est d'utiliser Image.getpixel / putpixel, mais c'est très lent. Il existe également une méthode utilisant numpy, mais étant donné qu'elle est fournie comme un outil, je souhaite réduire au maximum le nombre de modules dépendants.

Le nombre de modes de dessin étant très important, tous ne sont pas répertoriés. Cependant, la lecture de cet article facilitera la mise en œuvre d'autres modes de dessin.

J'ai fait un module Image4Layer

Implémentation des oreillers en mode mélange de PhotoShop.

https://github.com/pashango2/Image4Layer

L'installation peut être effectuée avec pip.

pip install image4layer

Il s'agit d'une implémentation de mode de fusion rapide utilisant la technique de cet article.

Utilisez le module ImageChops

Le module ImageChops vous permet de combiner facilement des images.

from PIL import Image, ImageChops

img = Image.open("sample.png ")
effect_img = Image.open("effect.png ")

B9BSxGZmEQpmAAAAAElFTkSuQmCC.pngkL+ySM465ToAlAAAAAElFTkSuQmCC.png

La gauche est l'image originale et la droite l'image de l'effet. Ces deux images seront utilisées comme échantillons.

Dodge (linéaire)

ImageChops.add(img, effect_img)

wE7Grw7M8iQMgAAAABJRU5ErkJggg==.png

Soustraire

ImageChops.subtract(img, effect_img)

X+81Pf98mz2EwAAAABJRU5ErkJggg==.png

Multiplier

ImageChops.multiply(img, effect_img)

8yo3kAAAAASUVORK5CYII=.png

écran

ImageChops.screen(img, effect_img)

P8BtLqhlqZTLrgAAAAASUVORK5CYII=.png

Comparaison (clair) / comparaison (sombre)

ImageChops.lighter(img, effect_img)
ImageChops.darker(img, effect_img)

X+81Pf98mz2EwAAAABJRU5ErkJggg==.pngwDMatqtY+79MgAAAABJRU5ErkJggg==.png

Valeur absolue de la différence

ImageChops.difference(img, effect_img)

diff.png

décalage

ImageChops.offset(img, 100, 100)

offset.png

Utilisez le module ImageMath

Les modes de dessin introuvables dans ImageChops sont implémentés dans le module ImageMath. Le module ImageMath est très original, mais une fois que vous vous y serez habitué, vous pourrez effectuer une conversion d'image gratuite à grande vitesse. Les points à noter sont les suivants.

Dans l'exemple suivant, la bande R est comparée (brillante).

from PIL import ImageMath

img_r = img.split()[0]
eff_r = effect_img.split()[0]

ImageMath.eval("convert(max(a, b), 'L')", a=img_r, b=eff_r)

AX6XflESotN9AAAAAElFTkSuQmCC.png

Il est également possible de spécifier un nombre égal, auquel cas la valeur sera 0 ou 1. Dans l'exemple suivant, la valeur 100 est définie pour les pixels avec une valeur de 128 ou moins.

ImageMath.eval("(a < 128) * 100", a=img_r).convert("L")

fill.png

Vous pouvez appeler des fonctions Python, mais gardez à l'esprit que l'image passée en argument est un objet arithmétique (ImageMath._Operand), pas un nombre de pixels.

Puisqu'il ne s'agit pas d'une valeur numérique, il est impossible de diviser le traitement par l'instruction if en fonction de la valeur du pixel. Si vous souhaitez diviser le traitement en fonction de la valeur du pixel, vous devez combiner des masques. Ce qui suit est un exemple de division du traitement en fonction de la valeur de 128 ou plus et de la valeur de 128 ou moins.

def _threshold(a):
    #255 si la valeur est de 128 ou moins, 1 si 128 ou plus/Définir sur 2
    div2 = a / 2 * (a >= 128)
    white = (a < 128) * 255
    return div2 + white

ImageMath.eval("func(a)", a=img_r, func=_threshold).convert("L")

white.png

Puisqu'il ne peut gérer que des bandes uniques, créez une série de fonctions qui décomposent, traitent et intègrent des bandes multiples. L'opération float a un degré de liberté plus élevé, je vais donc la convertir en float.

def _blend_f(bands1, bands2, func):
    blend = "convert(func(float(a), float(b)), 'L')"
    bands = [
        ImageMath.eval(
            blend,
            a=a,
            b=b,
            func=func
        )
        for a, b in zip(bands1, bands2)
    ]
    return Image.merge("RGB", bands)

Sur la base de ce qui précède, nous allons implémenter un mode de dessin compliqué.

recouvrir

def _over_lay(a, b):
    _cl = 2 * a * b / 255
    _ch = 2 * (a + b - a * b / 255) - 255
    return _cl * (a < 128) + _ch * (a >= 128)

_blend_f(img.split(), effect_img.split(), _over_lay)

overlay.png

Lumière douce

def _soft_light(a, b):
    _cl = (a / 255) ** ((255 - b) / 128) * 255
    _ch = (a / 255) ** (128 / b) * 255
    return _cl * (b < 128) + _ch * (b >= 128)

_blend_f(img.split(), effect_img.split(), _soft_light)

softlight.png

Lumière forte

def _hard_light(a, b):
    _cl = 2 * a * b / 255
    _ch = 2.0 * (a + b - a * b / 255.0) - 255.0
    return _cl * (b < 128) + _ch * (b >= 128)

_blend_f(img.split(), effect_img.split(), _hard_light)

hardlight.png

À propos de la vitesse de traitement

J'ai implémenté une superposition avec Image.putpixel pour comparer la vitesse de traitement.

def _put_pixel_overlay(a, b):
    c = Image.new(a.mode, a.size)
    for x in range(a.size[0]):
        for y in range(b.size[1]):
            cola = a.getpixel((x, y))
            colb = b.getpixel((x, y))
            
            colc = [
                _a * _b * 2 / 255 if _a < 128 else (2 *(_a + _b - _a * _b / 255) - 255)
                for _a, _b in zip(cola, colb)
            ]
            c.putpixel((x, y), tuple(int(_) for _ in colc))
    return c

La vitesse d'exécution est la suivante.

%timeit _put_pixel_overlay(img, effect_img)
1 loop, best of 3: 663 ms per loop

%timeit _blend_f(img.split(), effect_img.split(), _over_lay)
100 loops, best of 3: 5.63 ms per loop

La version ImageMath est 100 fois plus rapide.

Emballé

Nous avons téléchargé un package qui implémente le mode de dessin de Photoshop.

https://github.com/pashango2/Image4Layer

L'installation est facile avec pip.

pip install image4layer

finalement

En regardant le code PIL dans la rue, il y a des scènes où Image.getpixel / putpixel est utilisé.

Image.getpixel / putpixel est utilisé pour la création d'image, et c'est le dernier recours à utiliser pour la conversion d'image. Il existe des exemples de conversion d'image utilisant numpy, mais la mise en œuvre compacte de PIL uniquement est toujours intéressante. PIL est une bibliothèque compacte mais extrêmement puissante et rapide. Ayez une bonne vie PIL.

Recommended Posts

Implémentez des modes de dessin tels que PhotoShop à grande vitesse avec PIL / Pillow
Effectuez une conversion demi-largeur / pleine largeur à grande vitesse avec Python
Traitement d'image avec PIL (Pillow)