tintix
Anmeldungsdatum: 4. Juni 2010
Beiträge: Zähle...
|
Hallo allerseits, aus einem Bild möchte ich gerne alle Schwarzwerte (und Grauwerte) entfernen. Genauer: Ich habe mit einem Schwarzweißdrucker ein Liniennetz mit dünnen schwarzen Linien gedruckt, in das ich mit Füller und blauer Tinte reingeschrieben habe. Das Ergebnis habe ich gescannt und möchte nun das Liniennetz verschwinden lassen. Die Pixel des Liniennetzes sind natürlich nicht alle schwarz, sondern z. B. auch #1b1b1b. Die Pixel der Tinte sind natürlich nicht alle blau, sondern z. B. auch rgb(72,88,173) oder auch rgb(15,30,145). Mit GIMP kam ich nicht weiter und hoffe, dass imagemagick das kann. Probiert habe ich
convert scan.jpg -fuzz 30000 -fill white +opaque "rgb(72,88,173)" ausgabe.jpg
Damit werden aber zum einen die schwarzen Linien nicht entfernt, und Teile der blauen Schrift werden weiß gemacht. Mit welchen Parametern kann ich „Grau und Schwarz“ zu Weiß machen und „Blau“ erhalten? Für Tipps bin ich sehr dankbar!
|
Thomas_Do
Moderator
Anmeldungsdatum: 24. November 2009
Beiträge: 8544
|
Zeige einmal ein Beispiel. Die Vorgehensweise hängt stark von der Vorlage und der "Farbvarianz" ab.
|
seahawk1986
Anmeldungsdatum: 27. Oktober 2006
Beiträge: 11181
Wohnort: München
|
Die frage ist, ob du vom Scanner zuverlässig echte Grauwerte bekommst (also die R,G und B Werte für einen Pixel tatsächlich den selben Wert haben). Was bei dünnen Linien im Bild vielleicht einfacher ist, ist eine Kombination aus Erosion und Dilation:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 | import sys
import cv2
import numpy as np
def erode_and_dilate(img_path: str):
img = cv2.imread(img_path)
# convert to grayscale
grayImage = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# make a halftone image
thresh, bwImage = cv2.threshold(grayImage, 127, 255, cv2.THRESH_BINARY)
# the kernel for our operation
kernel = np.ones((5, 5), np.uint8)
# erode the halftone image
erosion = cv2.erode(bwImage, kernel, iterations=1)
# dilate the halftone image
dilation = cv2.dilate(erosion, kernel, iterations=1)
dilation_3c = cv2.cvtColor(dilation, cv2.COLOR_GRAY2BGR)
res = cv2.bitwise_or(img, dilation_3c)
cv2.imwrite(f"cleaned_{img_path}", res)
if __name__ == '__main__':
for arg in sys.argv[1:]:
erode_and_dilate(arg)
|
Das Skript benötigt die Pakete python3-opencv und python3-numpy. Du kannst es so auf Bilder loslassen, nachdem du es ausführbar gemacht hast:
./clean_thin_lines.py scan.jpg Im Anhang mal ein Beispiel-Ergebnis mit schwarzer und blauer Tinte auf einem College-Block.
- Bilder
|
tintix
(Themenstarter)
Anmeldungsdatum: 4. Juni 2010
Beiträge: 22
|
Unten sind zwei Beispielscans von mir: das unbearbeitete und das mit über GIMP erhöhtem Kontrast. Das Skript habe ich mit meinen extrem geringen Pythonkenntnissen mal gestartet (vorher noch python3-opencv installiert; python3-numpy war schon da): alles in eine Datei clean_thin_lines.py kopiert, ausführbar gemacht, in /home/ich/bin verschoben, dorthin auch die Datei scan_st1.jpg kopiert und dann ./clean_thin_lines.py gestartet. Das Skript lief einige Minuten und gab dann Fehlermeldungen aus. Dann habe ich ./clean_thin_lines.py 2> fehler abgesetzt; nach mehr als einer Stunde habe ich es mit Strg-C abgebrochen. Der Inhalt von fehler lautet wie folgt:
import-im6.q16: attempt to perform an operation not allowed by the security policy `PS' @ error/constitute.c/IsCoderAuthorized/408.
import-im6.q16: unable to read X window image `': Die Ressource ist zur Zeit nicht verfügbar @ error/xwindow.c/XImportImage/4978.
import-im6.q16: missing an image filename `cv2' @ error/import.c/ImportImageCommand/1289.
./clean_thin_lines.py: Zeile 5: Syntaxfehler beim unerwarteten Wort `('
./clean_thin_lines.py: Zeile 5: `def erode_and_dilate(img_path: str):'
Code Was lief hier falsch?
- Bilder
|
Thomas_Do
Moderator
Anmeldungsdatum: 24. November 2009
Beiträge: 8544
|
Das wird schwierig. Der Scanner liefert eben für Grau kein echtes Grau, sondern die Pixelwerte liegen, wie zu erwarten, um echte Grauwerte herum. Dazu kommt, dass die blaue Schrift über den fast schwarzen Linien, farblich kaum von den Linien selbst zu unterscheiden ist. Selbst manuell in Gimp konnte ich nur mäßig gute Ergebnisse erzielen. Wenn qualitativ hochwertige Grafiken erwünscht sind, sollte man den Ansatz ändern. Dazu könnte man z.B. die blaue Tinte auf blassroten (o.ä.) Hilfslinien verwenden, die man relativ leicht entfernen könnte.
|
seahawk1986
Anmeldungsdatum: 27. Oktober 2006
Beiträge: 11181
Wohnort: München
|
Sieht nach einem Konfigurationsproblem von Imagemagick aus, das intern von opencv genutzt wird, um das Bild aus dem jpeg-Format zu importieren - da muss man wie in https://imagemagick.org/script/security-policy.php erläutert in der /etc/ImageMagick-6/policy.xml die Berechtigung zum Ausführen von Operationen anpassen. Auf meinem System sieht das aktuell so aus:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96 | <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE policymap [
<!ELEMENT policymap (policy)+>
<!ATTLIST policymap xmlns CDATA #FIXED ''>
<!ELEMENT policy EMPTY>
<!ATTLIST policy xmlns CDATA #FIXED '' domain NMTOKEN #REQUIRED
name NMTOKEN #IMPLIED pattern CDATA #IMPLIED rights NMTOKEN #IMPLIED
stealth NMTOKEN #IMPLIED value CDATA #IMPLIED>
]>
<!--
Configure ImageMagick policies.
Domains include system, delegate, coder, filter, path, or resource.
Rights include none, read, write, execute and all. Use | to combine them,
for example: "read | write" to permit read from, or write to, a path.
Use a glob expression as a pattern.
Suppose we do not want users to process MPEG video images:
<policy domain="delegate" rights="none" pattern="mpeg:decode" />
Here we do not want users reading images from HTTP:
<policy domain="coder" rights="none" pattern="HTTP" />
The /repository file system is restricted to read only. We use a glob
expression to match all paths that start with /repository:
<policy domain="path" rights="read" pattern="/repository/*" />
Lets prevent users from executing any image filters:
<policy domain="filter" rights="none" pattern="*" />
Any large image is cached to disk rather than memory:
<policy domain="resource" name="area" value="1GP"/>
Define arguments for the memory, map, area, width, height and disk resources
with SI prefixes (.e.g 100MB). In addition, resource policies are maximums
for each instance of ImageMagick (e.g. policy memory limit 1GB, -limit 2GB
exceeds policy maximum so memory limit is 1GB).
Rules are processed in order. Here we want to restrict ImageMagick to only
read or write a small subset of proven web-safe image types:
<policy domain="delegate" rights="none" pattern="*" />
<policy domain="filter" rights="none" pattern="*" />
<policy domain="coder" rights="none" pattern="*" />
<policy domain="coder" rights="read|write" pattern="{GIF,JPEG,PNG,WEBP}" />
-->
<policymap>
<!-- <policy domain="system" name="shred" value="2"/> -->
<!-- <policy domain="system" name="precision" value="6"/> -->
<!-- <policy domain="system" name="memory-map" value="anonymous"/> -->
<!-- <policy domain="system" name="max-memory-request" value="256MiB"/> -->
<!-- <policy domain="resource" name="temporary-path" value="/tmp"/> -->
<policy domain="resource" name="memory" value="512MiB"/>
<policy domain="resource" name="map" value="512MiB"/>
<policy domain="resource" name="width" value="16KP"/>
<policy domain="resource" name="height" value="16KP"/>
<!-- <policy domain="resource" name="list-length" value="128"/> -->
<policy domain="resource" name="area" value="128MB"/>
<policy domain="resource" name="disk" value="1GiB"/>
<!-- <policy domain="resource" name="file" value="768"/> -->
<!-- <policy domain="resource" name="thread" value="4"/> -->
<!-- <policy domain="resource" name="throttle" value="0"/> -->
<!-- <policy domain="resource" name="time" value="3600"/> -->
<!-- <policy domain="coder" rights="none" pattern="MVG" /> -->
<!-- <policy domain="module" rights="none" pattern="{PS,PDF,XPS}" /> -->
<!-- <policy domain="delegate" rights="none" pattern="HTTPS" /> -->
<!-- <policy domain="path" rights="none" pattern="@*" /> -->
<!-- <policy domain="cache" name="memory-map" value="anonymous"/> -->
<!-- <policy domain="cache" name="synchronize" value="True"/> -->
<!-- <policy domain="cache" name="shared-secret" value="passphrase" stealth="true"/> -->
<!-- <policy domain="system" name="pixel-cache-memory" value="anonymous"/> -->
<!-- <policy domain="system" name="shred" value="2"/> -->
<!-- <policy domain="system" name="precision" value="6"/> -->
<!-- not needed due to the need to use explicitly by mvg: -->
<!-- <policy domain="delegate" rights="none" pattern="MVG" /> -->
<!-- use curl -->
<policy domain="delegate" rights="none" pattern="URL" />
<policy domain="delegate" rights="none" pattern="HTTPS" />
<policy domain="delegate" rights="none" pattern="HTTP" />
<!-- in order to avoid to get image with password text -->
<policy domain="path" rights="none" pattern="@*"/>
<!-- disable ghostscript format types -->
<policy domain="coder" rights="none" pattern="PS" />
<policy domain="coder" rights="none" pattern="PS2" />
<policy domain="coder" rights="none" pattern="PS3" />
<policy domain="coder" rights="none" pattern="EPS" />
<policy domain="coder" rights="read|write" pattern="PDF" />
<policy domain="coder" rights="none" pattern="XPS" />
</policymap>
|
Ansonsten sind deine Linien zu dick und zu schwarz, damit die von meinem Skript komplett ausgelöscht zu werden (dazu reicht die Halbtonumwandlung nicht aus) - wenn ich das etwas umstelle und nach HSV-Farbwerten filtere, bevor ich die Erosion/Dilation mache, sieht das Ergebnis recht passabel aus (aber dass er die Schrift an der Grundlinie abschneidet lässt sich so nicht vermeiden:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36 | #!/usr/bin/env python3
import pathlib
import sys
import cv2
import numpy as np
def erode_and_dilate(img_path: str, n_passes=1):
global img
path = pathlib.Path(img_path)
if not path.is_file(): return
target_folder = path.parent
target_name = f"cleared_{path.stem}{path.suffix}"
target = target_folder / target_name
img = cv2.imread(img_path)
# remove dark pixels
tmp = img.copy()
hsv = cv2.cvtColor(tmp, cv2.COLOR_BGR2HSV)
lower_blue = np.array([90, 50, 50])
upper_blue = np.array([180, 255, 255])
mask = cv2.inRange(hsv, lower_blue, upper_blue)
# the kernel for our operation
kernel = np.ones((3, 3), np.uint8)
erosion = cv2.erode(mask, kernel, iterations=n_passes)
dilation = cv2.dilate(erosion, kernel, iterations=n_passes)
dilation_3c = cv2.cvtColor(dilation, cv2.COLOR_GRAY2BGR)
mask = cv2.bitwise_not(dilation_3c)
res = cv2.bitwise_or(img, mask)
cv2.imwrite(str(target), res)
if __name__ == '__main__':
for arg in sys.argv[1:]:
erode_and_dilate(arg, 1)
|
- Bilder
|
tintix
(Themenstarter)
Anmeldungsdatum: 4. Juni 2010
Beiträge: 22
|
Ganz herzlichen Dank!!! Das Skript arbeitet schnell und tut, was man ihm sagt - ich staune! (Meine policy.xml unterschied sich nur in zwei Zeilen: In <policy domain="resource" name="memory" value="512MiB"/> hatte ich 256MiB stehen und in <policy domain="coder" rights="read|write" pattern="PDF" /> hatte ich "none" stehen. Nach Änderung hat es mit dem neuen Skript geklappt.) Ich habe gelernt: Meine blaue Tinte und die schwarzen Linien vertragen sich nicht; Dunkelblau und Schwarz sind wohl zu dicht beieinander; wahrscheinlich sind farbige Linien nötig. Daher wähle ich den von Thomas_Do vorgeschlagenen Weg und lasse mir im Kopierladen farbige Liniennetze ausdrucken. Frage an Thomas_Do: Ich hätte in meinem naiven Glauben Gelb als Komplementärfarbe von Blau gewählt. Was spricht für Blassrot?
|
Thomas_Do
Moderator
Anmeldungsdatum: 24. November 2009
Beiträge: 8544
|
tintix schrieb: Was spricht für Blassrot?
Nichts. Gelb ist sicher auch okay. Wichtig ist, dass es wenig Helligkeitsunterschied zwischen Linien und Hintergrund gibt. Deshalb "Blass...". Ideal wäre ein sehr heller grauer Hintergrund mit alternierenden farbigen Linien, die sich nach einer Graustufenkonvertierung nicht mehr vom Hintergrund unterscheiden. Dann kann man einen digitalen "Farbfilter" über das Bild legen und die Linien sind weg.
|
seahawk1986
Anmeldungsdatum: 27. Oktober 2006
Beiträge: 11181
Wohnort: München
|
Ansonsten kann auch eine Leuchtunterlage (die LED-basierten sind schlank und nicht mehr besonders teuer, z.B. https://www.real.de/product/358940386/ nehmen und eine in kräftigem schwarz gedruckte Lineatur unter das Schreibblatt legen - damit vermeidet man das Problem mit den Linien auf dem Blatt komplett (solange das Papier nicht zu dick ist).
|
tintix
(Themenstarter)
Anmeldungsdatum: 4. Juni 2010
Beiträge: 22
|
Vielen herzlichen Dank!! Damit kann ich arbeiten - mein Problem ist gelöst.
|