@Potatütata: Zum Quelltext selbst: Namen werden in Python klein_mit_unterstrichen geschrieben. Ausnahmen sind Konstanten (KOMPLETT_GROSS) und Klassen (MixedCase). Das "Script" im Modulnamen ist nicht wirklich sinnvoll.
Den Pfad zu den `venv`-Verzeichnissen steckt man nicht selbst *im* Programm in den Suchpfad für Module sondern man aktiviert vor dem Start des Programms das Virtualenv über die vorgesehenen Skripte. Das Programm selbst sollte nichts darüber wissen müssen ob es in einem venv läuft oder nicht.
``try``/``except`` um Importe zu legen nur um den Benutzer mit `print()` zu Informieren das ein Modul nicht importiert werden konnte und dann das Programm abzubrechen macht keinen Sinn. Genau das passiert ja auch wenn man diesen Code weg lässt. Zumal mir nicht so ganz klar ist wonach hier entschieden wurde ob normal importiert wird, oder mit dieser Sonderbehandlung, denn `numpy` könnte ja auch *nicht* installiert sein, das wird aber ohne irgendwelchen zusätzlichen Code importiert. `os` und `sys` kann man sich dann auch sparen wenn man diesen überflüssigen Code entfernt.
Das ohnehin schon abgekürzte `cv2` beim importieren auf `cv` zu verkürzen ist nicht wirklich sinnvoll.
Auf Modulebene sollte nur Code stehen der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht üblicherweise in einer Funktion die `main()` heisst. Daraus folgt, dass es keine Modulglobalen Variablen gibt, also Funktionen und Methoden alles was sie ausser Konstanten benötigen als Argument(e) übergeben bekommen.
Namen sollten keine kryptischen Abkürzungen enthalten und schon gar nicht nur eine kryptische Abkürzung sein. Wenn man `top_left_coordinates` meint, sollte man nicht nur `tl` schreiben.
Werte in Klammern beim Prompt von Eingaben sind in der Regel Defaultwerte die verwendet werden wenn man da *nichts* eingibt. Dass das hier nur Beispiele sind, ist sehr verwirrend für den Benutzer.
Wenn der Benutzer Zahlen eingeben soll, dann konvertiert man die Zeichenketteneingaben direkt nach der Eingabe in Zahlen und nicht jedes mal wenn man tatsächlich die Zahl braucht.
Der Dateipfad der Bilddatei sollte im Hauptprogramm vom Benutzer abgefragt werden. Sonst lässt sich die Funktion die das Bild lädt und in eine Bitmap wandelt schlecht testen und/oder wiederverwenden.
Daten wie Dateinamen wiederholt man nicht mehrfach über den gesamten Quelltext verteilt. Da definiert man am Anfang eine Konstante. Allerdings sehe ich den Sinn vom speichern und laden von "BWImg.png" über Funktionen hinweg nicht wirklich. Man kann das Bild auch einfach als Rückgabewert/Argument im Programm herum reichen. Da muss man nicht den Umweg über das Dateisystem gehen.
Warum wird nach der Anzeige des Bildes 10 Sekunden gewartet in denen nichts passiert? Würde mich als Benutzer ziemlich nerven.
`screensize` wird nirgends verwendet. Damit ist dann auch `ctypes` hinfällig und das Programm ist nicht mehr nur unter Windows lauffähig. `img_w` und `img_h` in `draw_image()` werden definiert aber nirgends verwendet.
Dann werden da mit dem Bild komische Dinge gemacht. Das ist bereits ein Numpy-Array, das heisst `np.array()` darauf aufzurufen bringt genau gar nichts. Der Name `n` ist auch sehr schlecht für ein Array das ein Bild enthält.
Wenn man zusätzlich zu den Werten eines iterierbaren Objekts noch einelaufende ganze Zahl benötigt, verwaltet man die nicht manuell, sondern verwendet `enumerate()`. Ich würde das auch eher nicht so frickelig kleinteilig lösen, sondern `itertools.groupby()` verwenden.
Das da in `coordinates` an jedem zweiten Index der Start- beziehungsweise Endwert einer Linie steht ist ungünstig und macht das Programm nur unnötig kompliziert. Das sollte eine Liste sein die als Elemente Paare von Start- und Endkoordinaten enthält.
Die `draw_image()`-Funktion sollte selbst dafür sorgen, dass am Ende die Maustaste nicht mehr gedrückt ist. Das sollte bei einem etwas übersichtlicheren Kontrollfluss auch leicht machbar sein.
Die Funktion macht zu viel. Das erstellen der Liniendaten sollte man dort heraus ziehen.
Für Zeitmessungen ist `time.monotonic()` besser geeignet als `time.time()`.
Das letzte `input()` ist überflüssig und nervig.
Ungetestet:
Code: Alles auswählen
#!/usr/bin/env python3
import time
from itertools import count, groupby
import cv2
import numpy as np
from more_itertools import last
from pynput.mouse import Button, Controller
def make_bitmap_image(
image_file_path, top_left_x, top_left_y, bottom_right_x, bottom_right_y
):
image = cv2.imread(str(image_file_path))
width, height = image.shape[:2]
factor = min(
(bottom_right_x - top_left_x) / width,
(bottom_right_y - top_left_y) / height,
)
_, bitmap_image = cv2.threshold(
cv2.cvtColor(
cv2.resize(image, (int(factor * width), int(factor * height))),
cv2.COLOR_BGR2GRAY,
),
127,
255,
cv2.THRESH_BINARY,
)
return bitmap_image
def iter_lines(image):
#
# FIXME This contains an error from the original implementation. The
# grouping just considers the X coordinates and completely ignores the Y
# coordinates. So the following image will give *one* line ((2, 0), (7,
# 1)), instead of two lines — one for each pixel row.
#
# ..###.....
# .....###..
#
y_coordinates, x_coordinates = np.where(
(image[:, :, 0:3] == [0, 0, 0]).all(2)
)
for _, group in groupby(
zip(count(), x_coordinates, y_coordinates),
key=lambda item: item[1] - item[0],
):
_, start_x, start_y = next(group)
_, end_x, end_y = last(group, (None, start_x, start_y))
yield ((start_x, start_y), (end_x, end_y))
def draw_image(lines, x_offset, y_offset):
mouse = Controller()
for (start_x, start_y), (end_x, _) in lines:
mouse.position = (start_x + x_offset, start_y + y_offset)
mouse.press(Button.left)
time.sleep(0.03)
for _ in range(start_x, end_x):
mouse.move(1, 0)
time.sleep(0.0002)
time.sleep(0.02)
mouse.release(Button.left)
time.sleep(0.04)
def main():
top_left_x, top_left_y = map(
int, input("Input top left Corner (x,y): ").split(",")
)
bottom_right_x, bottom_right_y = map(
int, input("Input bottom right Corner (x,y): ").split(",")
)
image_file_path = input(
"Input the path to the image (C:/Users/.../image_filename.ext): "
)
image = make_bitmap_image(
image_file_path, top_left_x, top_left_y, bottom_right_x, bottom_right_y
)
cv2.imshow("Press any key to accept the chosen image!", image)
cv2.waitKey()
start = time.monotonic()
draw_image(iter_lines(image), top_left_x, top_left_y)
print(f"Time for the drawing: {(time.monotonic() - start) / 60:.3f} min")
if __name__ == "__main__":
main()