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.
<iclass="fafa-arrow-right"aria-hidden="true">
Farbhalbton ist ein Prozess, bei dem Farben in CMYK getrennt und Halbtöne mit unterschiedlichen Winkeln überlagert werden. Der Effekt ähnelt dem von Farbdrucksachen.
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.
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.
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 °.
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.
Implementieren Sie den Halbton mit "pillow", einer Standard-Bildbibliothek von Python.
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 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")
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)
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)
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)
Sie können ein kleines Moiré sehen, aber ich denke, es ist von ausreichender Qualität.
Kairo ist eine Vektorzeichnungsbibliothek, die mit Vektoren zeichnet, sodass sie sehr schön ausgegeben werden kann. Sie können mit Kairo ausgeben, indem Sie die Funktion "dot_image" durch die folgende Funktion ersetzen.
def dot_image_by_cairo(size, dots, dot_radius, base_color=(0, 0, 0), dot_color=(1., 1., 1.), scale=1.0):
import cairo
w, h = tuple(int(x * scale) for x in img.size)
surface = cairo.ImageSurface(cairo.FORMAT_RGB24, w, h)
ctx = cairo.Context(surface)
ctx.set_source_rgb(*base_color)
ctx.rectangle(0, 0, w, h)
ctx.fill()
for x, y, color in dots:
fcolor = color / 255.
t = dot_radius * fcolor * scale
ctx.set_source_rgb(*dot_color)
ctx.arc(x, y, t, 0, 2 * math.pi)
ctx.fill()
return Image.frombuffer("RGBA", img.size, surface.get_data(), "raw", "RGBA", 0, 1)
#Ausgabe mit Kairo
dot_image_by_cairo(img.size, dots, 2.5, [1]*3, [0]*3, scale=1.)
Es ist besser als Supersampling, aber pycairo ist schwer zu installieren, daher bin ich persönlich der Meinung, dass Supersampling ausreicht.
Es ist im Grunde dasselbe wie in Schwarzweiß. Zerlegen Sie das Bild einfach in CMYK, erzeugen Sie Halbtöne für jedes Band in unterschiedlichen Winkeln und führen Sie die Bänder schließlich als CMYK zusammen.
Im folgenden Beispiel wird Cyan als 15 °, Gelb als 30 °, Schwarz als 45 ° und Magenta als 75 ° ausgegeben.
cmyk = img.convert("CMYK")
c, m, y, k = cmyk.split()
cdots = halftone(c, 15, 3)
mdots = halftone(m, 75, 3)
ydots = halftone(y, 35, 3)
kdots = halftone(k, 45, 3)
nc = dot_image(img.size, cdots, 2.5, scale=3.)
nm = dot_image(img.size, mdots, 2.5, scale=3.)
ny = dot_image(img.size, ydots, 2.5, scale=3.)
nk = dot_image(img.size, kdots, 2.5, scale=3.)
Image.merge("CMYK", [nc, nm, ny, nk]).convert("RGB")
Sie müssen nicht wissen, wann Sie Pillow verwenden, aber wenn Sie auf ein Smartphone usw. portieren, müssen Sie die Konvertierung von RGB-> CMYK, CMYK-> RGB selbst berechnen. Es gibt keine perfekte Konvertierungsformel zwischen RGB und CMYK, es handelt sich also nur um eine Pseudo-Formel. Das Folgende ist eine allgemeine Umrechnungsformel für RGB-> CMYK und CMYK->.
RGB->CMYK
C=(1-R-K)/(1-K)\\
M=(1-G-K)/(1-K)\\
Y=(1-B-K)/(1-K)\\
K=min(1-R,1-G,1-B)
CMYK->RGB
R=1-min(1,C*(1-K)+K)\\
G=1-min(1,M*(1-K)+K)\\
B=1-min(1,Y*(1-K)+K)
Es ist einsam, nur Halbtöne zu erzeugen, deshalb habe ich einen Filter im Cartoon-Stil mit Halbtönen erstellt. Lassen Sie Lena im Beispielbild wie einen Cartoon aussehen.
img = Image.open("lenna.png ")
Konvertieren Sie in Schwarzweiß, machen Sie Licht und Dunkelheit durch Ausgleichen klar und erhellen Sie dann den schwarzen Teil. Je nach Geschmack des Musters gehen die Details verloren, wenn Sie den Schattenteil zu dunkel machen. Machen Sie den schwarzen Teil also heller. Bitte würzen Sie diesen Bereich mit dem Bild des fertigen Entwurfs.
mono_img = img.convert("L")
mono_img = ImageOps.equalize(mono_img)
mono_img = mono_img.point(lambda x: x + 30 if x < 100 else x)
Reduzieren Sie die Farbe auf 4 Farben, um den Tonteil zu erstellen. Wenn die Details gut sind, sieht es wie ein Foto aus. Wenden Sie daher den Modusfilter an, um die feinen Details zu zerstören.
q_img = mono_img.quantize(4).convert("L")
q_img = q_img.filter(ImageFilter.ModeFilter(4))
Darauf wird die Halbtonverarbeitung angewendet.
dots = halftone(ImageOps.invert(q_img), 45, 4)
dot_img = dot_image(q_img.size, dots, 2, 0xFF, 0x00, scale=8).resize(q_img.size, Image.LANCZOS)
Überlagerungskomposite aus Halbton- und Schwarzweißbildern. Informationen zur Überlagerungszusammensetzungsmethode finden Sie unter "Implementieren von Zeichenmodi wie PhotoShop mit hoher Geschwindigkeit mit PIL / Pillow".
from PIL import ImageMath
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)
def over_lay(img1, img2):
eval_str = "func(float(a), float(b))"
return ImageMath.eval(eval_str, func=_over_lay, a=img1, b=img2).convert("L")
tone_img = over_lay(q_img, dot_img)
Der Rest wird durch Synthetisieren dieses Tonbildes und der Strichzeichnung abgeschlossen.
gray = img.convert("L")
gray2 = gray.filter(ImageFilter.MaxFilter(5))
line_inv = ImageChops.difference(gray, gray2)
line_img = ImageOps.invert(line_inv)
ImageChops.multiply(tone_img, line_img)
Der Farbhalbton ist ein sehr vielseitiger Filter, mit dem die Schatten eines Fotos so konvertiert werden können, dass sie wie ein Comic-Bildschirmton aussehen oder das Gefühl eines Drucks wie amerikanischer Comics vermitteln.
Der vollständige Code für die Halbtonverarbeitung ist auf Gist veröffentlicht.
https://gist.github.com/pashango2/487e5147015655a7fd6db3cbc1c7c833
Recommended Posts