[PYTHON] Über die Farbhalbtonverarbeitung von Bildern

Ich habe die Farbhalbtonverarbeitung von Bildern mit Pythons Bildbibliothek "Kissen" implementiert. Verwenden Sie diese Option, wenn Sie ein Foto in einen amerikanischen Stil konvertieren oder Schatten wie Bildschirmtöne reproduzieren möchten.

Halbton ist ein Effekt, der häufig für Malstile wie amerikanische Comics und Pop-Designs verwendet wird.

grad.png <iclass="fafa-arrow-right"aria-hidden="true"> grad2.png

Farbhalbton ist ein Prozess, bei dem Farben in CMYK getrennt und Halbtöne mit unterschiedlichen Winkeln überlagert werden. Der Effekt ähnelt dem von Farbdrucksachen.

rect4168.png

Anfangs hatte ich gehofft, dass es einfach zu implementieren sein würde, aber es war ein überraschend mühsamer Prozess. Es gab nur wenige technische Dokumente auf Japanisch, daher werde ich eine Notiz zum Teilen und Verbessern der Genauigkeit machen.

Übersicht über die Halbtonverarbeitung

Halbton ist eine Pseudodarstellung der Abstufung mit monochromatischen Punkten, die in einem Winkel im Bild angeordnet sind. Unter der Annahme, dass der Winkel 45 ° beträgt, der Abstand der Punktabstand ist und r der maximale Punktradius ist, ist das Ergebnis wie in der folgenden Abbildung gezeigt.

範囲を選択_025.png

Das Problem hierbei ist die Anordnung der Punkte entsprechend dem Winkel. Insbesondere wird das Originalbild gedreht und gescannt. Die folgende Abbildung zeigt den Scan bei einem Winkel von 45 °.

scan2.png

Im Fall eines Farbtons wird es synthetisiert, während der Winkel in Bezug auf jedes CMYK-Band verschoben wird. Es scheint, dass Cyan häufig auf 15 °, Gelb auf 30 °, Schwarz auf 45 ° und Magenta auf 75 ° eingestellt ist.

CMYK拡大.png

Umsetzung durch Kissen

Implementieren Sie den Halbton mit "pillow", einer Standard-Bildbibliothek von Python.

Nach Winkel scannen

Erstens ist es ein Winkelscan, der mit der koordinatenaffinen Transformationsmatrix einfach ist. Die zweidimensionale Rotationsmatrix ist wie folgt.

\begin{bmatrix}
cosθ & -sinθ \\
sinθ & cosθ
\end{bmatrix}
\begin{bmatrix}
x\\
y
\end{bmatrix}

Da die Matrixoperation in Python nicht standardmäßig bereitgestellt wird, wird durch Schließen eine Matrixoperationsfunktion generiert. Zu diesem Zeitpunkt wird ein Abstand mit Punktabstand empfangen, und wenn der Koordinatenwert um 1 zunimmt, wird eine Matrix erstellt, die ihn in ein Koordinatensystem konvertiert, das auf den benachbarten Abstand zeigt. Übrigens wird auch die inverse Matrix zurückgegeben, die vom Tonhöhenkoordinatensystem in das normale Koordinatensystem konvertiert wird.

def create_mat(_rot, _pitch):
    """Generieren Sie eine Matrix für das Tonhöhenkoordinatensystem und eine inverse Matrix für das normale Koordinatensystem"""
    _pi = math.pi * (_rot / 180.)
    _scale = 1.0 / _pitch

    def _mul(x, y):
        return (
            (x * math.cos(_pi) - y * math.sin(_pi)) * _scale,
            (x * math.sin(_pi) + y * math.cos(_pi)) * _scale,
        )

    def _imul(x, y):
        return (
            (x * math.cos(_pi) + y * math.sin(_pi)) * _pitch,
            (x * -math.sin(_pi) + y * math.cos(_pi)) * _pitch,
        )

    return _mul, _imul

Darüber hinaus erstellen wir einen Generator, der x und y scannt, da es sich beim Scannen um eine Mehrfachschleife von x und y handelt und die Quelle schwer zu erkennen ist. Auf diese Weise werden mehrere Schleifen eliminiert und Sie können klar schreiben.

def x_y_iter(w, h, sx, ex, sy, ey):
    fw, fh = float(w), float(h)
    for y in range(h + 1):
        ty = y / fh
        yy = (1. - ty) * sy + ty * ey
        for x in range(w + 1):
            tx = x / fw
            xx = (1. - tx) * sx + tx * ex
            yield xx, yy

Die Funktion zum Scannen des Bildes ist wie folgt.

def halftone(img, rot, pitch):
    mat, imat = create_mat(rot, pitch)
    
    w_half = img.size[0] // 2
    h_half = img.size[1] // 2
    pitch_2 = pitch / 2.
    
    #Begrenzungsrahmen berechnen
    bounding_rect = [
        (-w_half - pitch_2, -h_half - pitch_2),
        (-w_half - pitch_2, h_half + pitch_2),
    ]
    x, y = zip(*[mat(x, y) for x, y in bounding_rect])
    w, h = max(abs(t) for t in x), max(abs(t) for t in y)
    
    #Durchschnitt mit Gauß-Filter
    gmono = img.filter(ImageFilter.GaussianBlur(pitch / 2))
    
    #Führen Sie einen Scan durch,(x, y, color)Generieren Sie ein Array von
    dots = []
    for x, y in x_y_iter(int(w * 2) + 1, int(h * 2) - 1, -w, w, -h + 1., h - 1.):
        x, y = imat(x, y)
        x += w_half
        y += h_half
        if -pitch_2 < x < img.size[0] + pitch_2 and -pitch_2 < y < img.size[1] + pitch_2:
            color = gmono.getpixel((
                min(max(x, 0), img.size[0]-1),
                min(max(y, 0), img.size[1]-1)
            ))
            t = pitch_2 * (1.0 - (color / 255))
            dots.append((x, y, color))
    return dots

Generieren Sie ein Halbtonbild

Generieren Sie ein Halbtonbild aus dem gescannten Array.

def dot_image(size, dots, dot_radius, base_color=0, dot_color=0xFF, scale=1.0):
    img = Image.new("L", tuple(int(x * scale) for x in size), base_color)
    draw = ImageDraw.Draw(img)

    for x, y, color in dots:
        t = dot_radius * (color / 255) * scale
        x *= scale
        y *= scale
        draw.ellipse((x - t, y - t, x + t, y + t), dot_color)
    
    return img

Lassen Sie uns nun ein monochromes Halbtonbild erzeugen.

img = Image.open("sample.png ").convert("L")

ダウンロード (11).png

Bei der Monochrom-Konvertierung möchten wir den schwarzen Teil mit Punkten ausdrücken, anstatt den weißen Teil mit Punkten auszudrücken, sodass die Farbe invertiert wird.

img = ImageOps.invert(img)

ダウンロード (13).png

Generieren Sie ein Halbtonbild mit rot = 45 °, Pitch = 3, dot_radius = 2,5.

# rot=45°, pitch=3, dot_radius=2.Konvertieren Sie mit 5
dots = halftone(img, 45, 3)

#Zurück ist 0xFF,Halbtonbilderzeugung mit Punkten 0x00
dot_image(img.size, dots, 2.5, 0xFF, 0x00)

ダウンロード (14).png

schmutzig···

Der Grund, warum es so schmutzig ist, ist, dass ImageDraw von Pillow kein Anti-Aliasing unterstützt, sodass das Punkt-für-Punkt-Zeichnen ein Aliasing verursacht. Wenn Sie ImageDraw mit Kissen gegen Aliasing verwenden möchten, führen Sie etwa acht Mal eine Super-Stichprobe durch. (Abtasten eines Bildes, das größer als das Ausgabebild ist, und Reduzieren, um das Aliasing zu verringern)

#8-mal supersamtern und reduzieren
dot_image(img.size, dots, 2.5, 0xFF, 0x00, scale=8.).resize(img.size, Image.LANCZOS)

ダウンロード (15).png

Sie können ein kleines Moiré sehen, aber ich denke, es ist von ausreichender Qualität.