Initial commit with project files

This commit is contained in:
2025-06-27 14:34:11 +02:00
commit 7ea3207e63
310 changed files with 9331 additions and 0 deletions

144
CV-App/README.md Normal file
View File

@@ -0,0 +1,144 @@
# CV-Application
Original | Geom. Transformation | Chrominanz
:-------------------------:|:-------------------------:|:-------------------------:
![](./data/cv1.png) | ![](./data/cv2.png) | ![](./data/cv3.png)
Die CV-App ist eine Applikation, mit der eine Bildverarbeitungs-Pipeline generiert werden kann. Die Pipeline ließt eine
vorhandene Webcam aus. Der Inhalt dieses Videostreams wird dann durch CV-Algorithmen be- und/oder verarbeitet und angezeigt.
Optional kann der so erzeugte Videostream an eine virtuelle Kamera weitergeleitet werden. Diese virtuelle Kamera kann
dann von anderen Programmen (z.B. für Videokonferenzen) wie eine normale Webcam ausgelesen werden.
Zwei einfache Algorithmen wie die geometrische Transformation oder die Entfernung der Luminanz sind in oberen
Abbildungen dargestellt.
## Anleitung
### Treiber virtuelle Kamera
Die Grundfunktion der CV-App ist einsatzbereit, sobald dieses Repository erfolgreich installiert ist. Sie können Ihre
Webcam einlesen und CV-Algorithmen auf den Videostream anwenden.
Für die Nutzung der virtuellen Kamera ist ein zusätzlicher Treiber notwendig. Je nachdem welches Betriebssystem Sie
nutzen, kann dieser variieren. Die nötige Treiber Installation finden Sie unter
[https://github.com/letmaik/pyvirtualcam](https://github.com/letmaik/pyvirtualcam).
### Bedienung des Programms
Führen Sie das Skript `main.py` aus diesem Verzeichnis mit dem Befehl
```bash
python main.py --camera=0 --mode=virtual_cam --video=PFAD_ZU_EINEM_VIDEO
```
im Terminal aus. Dabei stehen Ihnen einige optionale Parameter zur Verfügung. Wenn Sie die Parameter nicht angeben,
werden die Default-Werte verwendet. Die Bedeutung der Parameter sowie die Default-Werte finden Sie in der folgenden
Tabelle.
**Parameter** | **Default-Wert** | **Beschreibung**
:---:|:---:|:---:|
--camera| 0 | OpenCV ID der Kamera. Wenn -1 angegeben ist, wird anstelle einer Kamera ein Video in Dauserschleife gespielt.
--mode| *virtual_cam* | Entweder *virtual_cam* (mit virtueller Kamera und Bildschirmausgabe) oder *screen* (nur Bildschirmausgabe)
--video | - | Gibt den Pfad zum Video an, wenn --camera=-1 ist
**Hinweise:**
- Sollten Sie keine Kamera zur Verfügung haben, können Sie *--camera=-1* wählen, um ein Video zu verwenden
- Die Default-Werte sind in `main.py` definiert und können dort angepasst werden
Nachdem Sie das Programm erfolgreich gestartet haben, sollten Sie das Bild der Kamera in einem neu geöffneten Fenster
sehen. Zu Beginn der Programmausführung wird kein CV-Algorithmus auf das Bild angewendet (Eingangsbild=Ausgangsbild).
Sie können verschiedene Funktionen bzw. Algorithmen durch betätigen verschiedener Tasten aktivieren. Als Standard sind
einige Funktionen auf den Tasten *1* bis *10* vorprogrammiert. Es ist ebenfalls möglich, mit Maus-Aktionen mit der
Pipeline zu interagieren.
Mit den Tasten **f** und **e** können Sie den Auto**f**okus bzw. Auto**e**xposure aktivieren oder deaktivieren.
**Hinweise:**
- Sie können nur mit der App interagieren, wenn das Programmfenster im Vordergrund ist!
- Autofokus und Autoexposure sind für viele Webcams nicht supported!
## Eigene CV Algorithmen
Für die Implementierung eigener Algorithmen sind nur Dateien in dem Unterverzeichnis *algorithms* notwendig. Öffnen
Sie sich in das Verzeichnis und lesen die folgenden Abschnitte. Nachdem Sie die Abschnitte gelesen haben können Sie die Übungsaufgabe
in der Datei [exercise.md](./exercise.md) bearbeiten.
### Eigenen "Algorithm" erstellen
Sie können einen eigenen Algorithmus erstellen, in dem Sie ein neues Skript in dem Ordner *algorithms* erstellen. Das
folgende Skript *algorithms/your_algorithm.py* zeigt einen beispielhaften Algorithmus, der einen Weißabgleich implementiert.
```python
import cv2
import numpy as np
from . import Algorithm
class YourAlgorithm(Algorithm):
""" The implementation of your algorithm """
def __init__(self):
""" Inititation of your algorithm. You can store member variables here! """
self.max_b, self.max_g, self.max_r = 255, 255, 255
self.last_image = None
def process(self, img):
""" Here the input image (img) is processed and returned """
self.last_image = img
img = img.astype(np.float32)
img[:, :, 0] = np.clip(img[:, :, 0], 0, self.max_b) * 255 / max(1, self.max_b)
img[:, :, 1] = np.clip(img[:, :, 1], 0, self.max_g) * 255 / max(1, self.max_g)
img[:, :, 2] = np.clip(img[:, :, 2], 0, self.max_r) * 255 / max(1, self.max_r)
img = img.astype(np.uint8)
return img
def mouse_callback(self, event, x, y, flags, param):
""" The mouse callback react on mouse events """
if self.last_image is None:
return
if event == cv2.EVENT_LBUTTONUP:
self.max_b, self.max_g, self.max_r = \
self.last_image[y, x, 0], self.last_image[y, x, 1], self.last_image[y, x, 2]
```
Die Funktion *\_\_init\_\_(self)* wird bei der Erstellung des Algorithmus aufgerufen. Sie können dort Variablen definieren,
die während der gesamten Laufzeit gespeichert bleiben. So können Sie z.B. Daten zwischen der Eingabe mehrerer Bilder
speichern.
Die Funktion *process.py(self, img)* verarbeitet jedes ausgelesene Bild. Am Ende der Funktion **muss** ein Bild mit selber Höhe und
Breite ausgegben werden.
Die Funktion *mouse_callback(self, event, x, y, flags, param)* wird bei Maus-Events ausgeführt. Für weitere Information
lesen Sie z.B. [hier](https://techtutorialsx.com/2020/12/08/python-opencv-mouse-events/).
In dem Ordner *algorithms* sind mehrere Beispiele für Algorithmen gegeben.
### Verlinken des eigenen Algorithmus zu einer Taste
Ihr Algorithmus *YourAlgorithm* kann nun zu einer Taste verlinkt werden. Der folgende Code entspricht in etwa dem Inhalt
der Datei *\_\_init\_\_.py*. Ihr Algorithmus ist in dem Beispiel an die Taste *3* verlinkt. Um weitere Algorithmen zu
verlinken müssen Sie lediglich einen weiteren Import und einen Eintrag in das algorithmus-dictionary hinzufügen.
```python
class Algorithm:
def process(self, img):
return img
def mouse_callback(self, event, x, y, flags, param):
return
from .image_to_gray import ImageToGray
from .image_to_hue import ImageToHue
from .your_algorithm import YourAlgorithm
algorithms = dict()
algorithms["0"] = Algorithm
algorithms["1"] = ImageToGray
algorithms["2"] = ImageToHue
algorithms["3"] = YourAlgorithm
```
## Anforderungen
Hardware:
- Webcam, die von OpenCV eingelesen werden kann
Getestet mit Python Versionen:
- 3.6
Getestet auf Betriebssystemen:
- Windows 10
- OpenSuse (pyvirtualcam funktioniert nicht!)

View File

@@ -0,0 +1,38 @@
class Algorithm:
""" An abstract class to create custom algorithms """
def process(self, img):
""" Processes the input image"""
return img
def mouse_callback(self, event, x, y, flags, param):
""" Reacts on mouse callbacks """
return
''' Import algorithms to use'''
from .image_to_gray import ImageToGray
from .image_to_hue import ImageToHue
from .motion_detector import MotionDetector
from .white_balancing import WhiteBalancing
from .spin import Spin
from .segmentation_tracker import SegmentationTracker
#from .object_detection import ObjectDetector
#from .bottle_detection import BottleDetector
from .invis_cloak import InvisCloak
from .canny_edges import CannyEdgeDetector
''' Link Algorithms to keys '''
algorithms = dict()
algorithms["0"] = Algorithm
algorithms["1"] = ImageToGray
algorithms["2"] = ImageToHue
algorithms["3"] = MotionDetector
algorithms["4"] = WhiteBalancing
algorithms["5"] = Spin
algorithms["6"] = SegmentationTracker
algorithms["7"] = InvisCloak
#algorithms["7"] = ObjectDetector
#algorithms["8"] = BottleDetector
algorithms["9"] = CannyEdgeDetector

View File

@@ -0,0 +1,69 @@
"""
Many thanks to https://github.com/vardanagarwal/Proctoring-AI/blob/master/coco models/tflite mobnetv1 ssd
"""
import cv2
import numpy as np
import threading
from copy import copy
from time import sleep
from . object_detection import Detector
from . import Algorithm
class BottleDetector(Algorithm):
""" Detects objects """
def __init__(self):
""" Init some values and set seed point to None """
self.objects = dict()
self.detection_image = None
self.lock = threading.Lock()
self.thread = threading.Thread(target=self._detect, args=[], daemon=True)
self.thread.start()
def process(self, img):
"""
Tries to segment a region around the seed point and calculates a new seed point by finding the segments center
"""
with self.lock:
if self.detection_image is None:
self.detection_image = np.copy(img)
with self.lock:
objects = copy(self.objects)
h, w, c = img.shape
if "detection_classes_name" in objects.keys():
for i, cls in enumerate(objects["detection_classes_name"]):
name = cls["name"]
if name in ["bottle", "cup"]:
box = objects["detection_boxes"][i]
score = objects["detection_scores"][i]
y1, x1, y2, x2 = \
max(round(box[0] * h) - 20, 0), round(box[1] * w) - 20 ,\
max(round(box[2] * h) + 20, 0), round(box[3] * w) + 20
if img[y1:y2, x1:x2].size > 0:
img[y1:y2, x1:x2] = cv2.medianBlur(img[y1:y2, x1:x2], 31)
return img.astype(np.uint8)
def _detect(self):
detector = Detector()
while True:
with self.lock:
img = self.detection_image
if img is None:
sleep(.033)
continue
objects = detector.make_inference(img, score_thresh=0.1)
with self.lock:
self.objects = objects
self.detection_image = None
def mouse_callback(self, event, x, y, flags, param):
""" Selects a new seed point"""
if event == cv2.EVENT_LBUTTONUP:
pass

View File

@@ -0,0 +1,40 @@
import cv2
import numpy as np
from . import Algorithm
class CannyEdgeDetector(Algorithm):
""" Converts a BGR image to grayscale"""
def __init__(self):
self.image_count = 0
self.background = None
self.background_update_rate = 0.2
self.threshold = 15
def process(self, img):
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
h, w = img_gray.shape
resized_image = cv2.resize(img_gray, (int(w/2), int(h/2)), interpolation=cv2.INTER_NEAREST)
blurred_img = cv2.GaussianBlur(resized_image, (15, 15), 0)
if self.background is None:
self.background = blurred_img
self.background = (1 - self.background_update_rate) * self.background + self.background_update_rate * blurred_img
diff = blurred_img - self.background
diff_abs = np.abs(diff)
binary_image = diff_abs > self.threshold
canny_edges = canny(resized_image, 50, 100)
canny_edges = canny_edges * binary_image
canny_edges = cv2.resize(canny_edges, (int(w), int(h)), interpolation=cv2.INTER_NEAREST)
return canny_edges
def canny(img, thresh1, thresh2):
img = cv2.Canny(img, thresh1, thresh2)
return img

View File

@@ -0,0 +1,10 @@
import cv2
from . import Algorithm
class ImageToGray(Algorithm):
""" Converts a BGR image to grayscale"""
def process(self, img):
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
return img

View File

@@ -0,0 +1,13 @@
import cv2
import numpy as np
from . import Algorithm
class ImageToHue(Algorithm):
""" Normalizes a BGR image with color information"""
def process(self, img):
channel_sum = np.sum(img.astype(np.float32), axis=2, keepdims=True)
img_normalized = img.astype(np.float32) * 255 / channel_sum
img_normalized = img_normalized.astype(np.uint8())
return img_normalized

View File

@@ -0,0 +1,118 @@
import cv2
import numpy as np
from copy import deepcopy
from matplotlib import pyplot as plt
from . import Algorithm
class InvisCloak (Algorithm):
""" init function """
def __init__(self):
pass
""" Processes the input image"""
def process(self, img):
""" 2.1 Vorverarbeitung """
""" 2.1.1 Rauschreduktion """
plotNoise = False # Schaltet die Rauschvisualisierung ein
if plotNoise:
self._plotNoise(img, "Rauschen vor Korrektur")
img = self._211_Rauschreduktion(img)
if plotNoise:
self._plotNoise(img, "Rauschen nach Korrektur")
""" 2.1.2 HistogrammSpreizung """
img = self._212_HistogrammSpreizung(img)
""" 2.2 Farbanalyse """
""" 2.2.1 RGB """
self._221_RGB(img)
""" 2.2.2 HSV """
self._222_HSV(img)
""" 2.3 Segmentierung und Bildmdifikation """
img = self._23_SegmentUndBildmodifizierung(img)
return img
""" Reacts on mouse callbacks """
def mouse_callback(self, event, x, y, flags, param):
if event == cv2.EVENT_LBUTTONUP:
print("A Mouse click happend! at position", x, y)
def _plotNoise(self, img, name:str):
height, width = np.array(img.shape[:2])
centY = (height / 2).astype(int)
centX = (width / 2).astype(int)
cutOut = 5
tmpImg = deepcopy(img)
tmpImg = tmpImg[centY - cutOut:centY + cutOut, centX - cutOut:centX + cutOut, :]
outSize = 500
tmpImg = cv2.resize(tmpImg, (outSize, outSize), interpolation=cv2.INTER_NEAREST)
cv2.imshow(name, tmpImg)
cv2.waitKey(1)
def _211_Rauschreduktion(self, img):
"""
Hier steht Ihr Code zu Aufgabe 2.1.1 (Rauschunterdrückung)
- Implementierung Mittelwertbildung über N Frames
"""
return img
def _212_HistogrammSpreizung(self, img):
"""
Hier steht Ihr Code zu Aufgabe 2.1.2 (Histogrammspreizung)
- Transformation HSV
- Histogrammspreizung berechnen
- Transformation BGR
"""
return img
def _221_RGB(self, img):
"""
Hier steht Ihr Code zu Aufgabe 2.2.1 (RGB)
- Histogrammberechnung und Analyse
"""
pass
def _222_HSV(self, img):
"""
Hier steht Ihr Code zu Aufgabe 2.2.2 (HSV)
- Histogrammberechnung und Analyse im HSV-Raum
"""
pass
def _23_SegmentUndBildmodifizierung (self, img):
"""
Hier steht Ihr Code zu Aufgabe 2.3.1 (StatischesSchwellwertverfahren)
- Binärmaske erstellen
"""
"""
Hier steht Ihr Code zu Aufgabe 2.3.2 (Binärmaske)
- Binärmaske optimieren mit Opening/Closing
- Wahl größte zusammenhängende Region
"""
"""
Hier steht Ihr Code zu Aufgabe 2.3.1 (Bildmodifizerung)
- Hintergrund mit Mausklick definieren
- Ersetzen des Hintergrundes
"""
return img

View File

@@ -0,0 +1,45 @@
import cv2
import numpy as np
from . import Algorithm
class MotionDetector(Algorithm):
""" Converts a BGR image to grayscale"""
def __init__(self):
self.image_count = 0
self.background = None
self.motion_field = None
self.background_update_rate = 0.5
self.motion_update_rate = 0.3
self.threshold = 50
def process(self, img):
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
h, w = img_gray.shape
blurred_img = cv2.resize(img_gray, (int(w/2), int(h/2)), interpolation=cv2.INTER_NEAREST)
blurred_img = cv2.GaussianBlur(blurred_img, (15, 15), 0)
if self.background is None:
self.background = blurred_img
self.motion_field = np.zeros_like(blurred_img)
self.background = (1 - self.background_update_rate) * self.background + self.background_update_rate * blurred_img
diff = blurred_img - self.background
diff_abs = np.abs(diff)
diff_rel = np.clip(diff_abs, 0, self.threshold) / self.threshold
self.motion_field = (1 - self.motion_update_rate) * self.motion_field + self.motion_update_rate * diff_rel
motion_field = cv2.resize(self.motion_field, (w, h), interpolation=cv2.INTER_NEAREST)
motion_field = np.expand_dims(motion_field, 2)
colormap = cv2.applyColorMap((motion_field * 255).astype(np.uint8), cv2.COLORMAP_HOT)
img_gray = np.stack([img_gray, img_gray, img_gray], axis=2)
final_image = 0.5 * img_gray * (1 - motion_field) + colormap * motion_field
final_image = final_image.astype(np.uint8)
self.image_count += 1
return final_image

View File

@@ -0,0 +1,179 @@
"""
Many thanks to https://github.com/vardanagarwal/Proctoring-AI/blob/master/coco models/tflite mobnetv1 ssd
"""
import cv2
import numpy as np
import multiprocessing
import threading
import os
from copy import copy
from time import sleep
from . import Algorithm
""" Check if neural network accelerator is existing """
try_edgetpu = True
try:
if not try_edgetpu:
raise Exception()
from pycoral.adapters import common
from pycoral.adapters import detect
from pycoral.utils.dataset import read_label_file
from pycoral.utils.edgetpu import make_interpreter, list_edge_tpus
if len(list_edge_tpus()) == 0:
raise Exception()
engine = "edgetpu"
except Exception as e:
import tensorflow as tf
engine = "tflite"
class Detector:
def __init__(self):
self.category_index = self.create_category_index()
if engine == "tflite":
self.num_threads = int(multiprocessing.cpu_count())
print("Self using %s threads for object detection" % self.num_threads)
self.interpreter = tf.lite.Interpreter(
model_path="data" + os.sep + "ssd_mobilenet_v2_coco_quant_postprocess.tflite", num_threads=self.num_threads
)
self.interpreter.allocate_tensors()
# Get input and output tensors.
self.input_details = self.interpreter.get_input_details()
self.output_details = self.interpreter.get_output_details()
elif engine == "edgetpu":
print("Running with edge tpu")
self.interpreter = make_interpreter("data" + os.sep + "ssd_mobilenet_v2_coco_quant_postprocess_edgetpu.tflite")
self.interpreter.allocate_tensors()
# Get input and output tensors.
self.input_details = self.interpreter.get_input_details()
self.output_details = self.interpreter.get_output_details()
def create_category_index(self, label_path='data' + os.sep + 'labelmap.txt'):
f = open(label_path)
category_index = {}
for i, val in enumerate(f):
if i != 0:
val = val[:-1]
category_index.update({(i - 1): {'id': (i - 1), 'name': val}})
f.close()
return category_index
def get_output_dict(self, nms=True, iou_thresh=0.5, score_thresh=0.5):
output_dict = {
'detection_boxes': self.interpreter.get_tensor(self.output_details[0]['index'])[0],
'detection_classes': self.interpreter.get_tensor(self.output_details[1]['index'])[0],
'detection_scores': self.interpreter.get_tensor(self.output_details[2]['index'])[0],
'num_detections': self.interpreter.get_tensor(self.output_details[3]['index'])[0]
}
output_dict['detection_classes'] = output_dict['detection_classes'].astype(np.int64)
output_dict["detection_classes_name"] = [self.category_index[x] for x in output_dict["detection_classes"]]
if nms and engine == "tflite":
output_dict = self.apply_nms(output_dict, iou_thresh, score_thresh)
if nms and engine == "edgetpu":
valid = np.where(output_dict["detection_scores"] >= score_thresh)[0]
if valid.size == 0:
output_dict = {}
elif valid.size == 1:
output_dict = {
'detection_boxes': output_dict["detection_boxes"][valid[0]:valid[0] + 1],
'detection_classes': output_dict["detection_classes"][valid[0]:valid[0] + 1],
'detection_scores': output_dict["detection_scores"][valid[0]:valid[0] + 1],
'detection_classes_name': output_dict["detection_classes_name"][valid[0]:valid[0] + 1],
'num_detections': 1,
}
else:
output_dict = {
'detection_boxes': output_dict["detection_boxes"][valid],
'detection_classes': output_dict["detection_classes"][valid],
'detection_scores': output_dict["detection_scores"][valid],
'detection_classes_name': [x for i,x in enumerate(output_dict["detection_classes_name"]) if i in valid],
'num_detections': valid.size,
}
return output_dict
def apply_nms(self, output_dict, iou_thresh=0.5, score_thresh=0.5):
q = 90 # no of classes
num = int(output_dict['num_detections'])
boxes = np.zeros([1, num, q, 4])
scores = np.zeros([1, num, q])
# val = [0]*q
for i in range(num):
# indices = np.where(classes == output_dict['detection_classes'][i])[0][0]
boxes[0, i, output_dict['detection_classes'][i], :] = output_dict['detection_boxes'][i]
scores[0, i, output_dict['detection_classes'][i]] = output_dict['detection_scores'][i]
nmsd = tf.image.combined_non_max_suppression(
boxes=boxes, scores=scores, max_output_size_per_class=num, max_total_size=num, iou_threshold=iou_thresh,
score_threshold=score_thresh, pad_per_class=False, clip_boxes=False
)
valid = nmsd.valid_detections[0].numpy()
output_dict = {
'detection_boxes': nmsd.nmsed_boxes[0].numpy()[:valid],
'detection_classes': nmsd.nmsed_classes[0].numpy().astype(np.int64)[:valid],
'detection_scores': nmsd.nmsed_scores[0].numpy()[:valid],
'detection_classes_name': output_dict["detection_classes_name"][:valid]
}
return output_dict
def make_inference(self, img, nms=True, score_thresh=0.5, iou_thresh=0.5):
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
img_rgb = cv2.resize(img_rgb, (300, 300), cv2.INTER_AREA)
img_rgb = img_rgb.reshape([1, 300, 300, 3])
self.interpreter.set_tensor(self.input_details[0]['index'], img_rgb)
self.interpreter.invoke()
output_dict = self.get_output_dict(nms, iou_thresh, score_thresh)
return output_dict
class ObjectDetector(Algorithm):
""" Detects objects """
def __init__(self):
""" Init some values and set seed point to None """
self.objects = dict()
self.detection_image = None
self.lock = threading.Lock()
self.thread = threading.Thread(target=self._detect, args=[], daemon=True)
self.thread.start()
def process(self, img):
"""
Tries to segment a region around the seed point and calculates a new seed point by finding the segments center
"""
with self.lock:
if self.detection_image is None:
self.detection_image = np.copy(img)
with self.lock:
objects = copy(self.objects)
h, w, c = img.shape
if "detection_classes_name" in objects.keys():
for i, cls in enumerate(objects["detection_classes_name"]):
box = objects["detection_boxes"][i]
score = objects["detection_scores"][i]
name = cls["name"]
y1, x1, y2, x2 = round(box[0] * h), round(box[1] * w), round(box[2] * h), round(box[3] * w)
img = cv2.rectangle(img, (x1, y1), (x2, y2), color=(0, 0, 0), thickness=2)
img = cv2.putText(img, "%s: %.2f" % (name, score), (x1, y1), fontFace=cv2.FONT_HERSHEY_SIMPLEX, fontScale=1, color=(0, 0, 0))
return img
def _detect(self):
detector = Detector()
while True:
with self.lock:
img = self.detection_image
if img is None:
sleep(.033)
continue
objects = detector.make_inference(img)
with self.lock:
self.objects = objects
self.detection_image = None
def mouse_callback(self, event, x, y, flags, param):
""" Selects a new seed point"""
if event == cv2.EVENT_LBUTTONUP:
pass

View File

@@ -0,0 +1,71 @@
import cv2
import numpy as np
from . import Algorithm
class SegmentationTracker(Algorithm):
""" Tracks a point by re-identify a suitable segmentation """
def __init__(self):
""" Init some values and set seed point to None """
self.pos = None
self.distance_threshold = 80
self.reference_pixel = None
def process(self, img):
"""
Tries to segment a region around the seed point and calculates a new seed point by finding the segments center
"""
if self.pos is None:
result = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
result = np.stack([result, result, result], axis=2)
return result
h, w, c = img.shape
if self.reference_pixel is None:
self.reference_pixel = np.copy(img[self.pos[1], self.pos[0]])
pixel_low, pixel_high = \
np.maximum(0, self.reference_pixel-self.distance_threshold),\
np.minimum(255, self.reference_pixel+self.distance_threshold)
binary = cv2.inRange(img, pixel_low, pixel_high)
element = np.ones((5, 5), dtype=np.uint8)
binary = cv2.erode(binary, element)
binary = cv2.dilate(binary, element)
sure_background = binary
sure_foreground = np.zeros_like(sure_background)
x, y = max(2, self.pos[0]), max(2, self.pos[1])
sure_foreground[y-2:y+2, x-5:x+2] = 1
unknown = np.maximum(0, sure_background - sure_foreground)
ret, markers = cv2.connectedComponents(sure_foreground)
# Add one to all labels so that sure background is not 0, but 1
markers = markers + 1
# Now, mark the region of unknown with zero
markers[unknown == 255] = 0
markers = cv2.watershed(img, markers)
try:
contours, hierarchy = cv2.findContours(((markers == 2) * 1).astype(np.uint8), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
c = contours[0]
M = cv2.moments(c)
cX = int(M["m10"] / M["m00"])
cY = int(M["m01"] / M["m00"])
self.pos = (min(cX, w-1), min(cY, h-1))
except:
pass
result = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
result = np.stack([result, result, result], axis=2)
random_noise = np.random.randint(0, 255, (h, w), dtype=np.uint8)
random_noise = cv2.applyColorMap(random_noise, colormap=cv2.COLORMAP_INFERNO)
result[markers == 2] = random_noise[markers == 2]
return result
def mouse_callback(self, event, x, y, flags, param):
""" Selects a new seed point"""
if event == cv2.EVENT_LBUTTONUP:
self.pos = (x, y)
self.reference_pixel = None

View File

@@ -0,0 +1,131 @@
import cv2
import numpy as np
from copy import deepcopy
from matplotlib import pyplot as plt
from . import Algorithm
class SilhouetteGhost (Algorithm):
""" init function """
def __init__(self):
self.image_count = 0
self.background = None
self.background_update_rate = 0.2
self.threshold = 15
""" Processes the input image"""
def process(self, img):
""" 2.1 Vorverarbeitung """
""" 2.1.1 Rauschreduktion """
plotNoise = False # Schaltet die Rauschvisualisierung ein
if plotNoise:
self._plotNoise(img, "Rauschen vor Korrektur")
img = self._211_Rauschreduktion(img)
if plotNoise:
self._plotNoise(img, "Rauschen nach Korrektur")
""" 2.1.2 HistogrammSpreizung """
img = self._212_HistogrammSpreizung(img)
""" 2.2 Vordergrund-Detektion """
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
h, w = img_gray.shape
resized_img_gray = cv2.resize(img_gray, (int(w/2), int(h/2)), interpolation=cv2.INTER_NEAREST)
blurred_img = cv2.GaussianBlur(resized_img_gray, (15, 15), 0)
if self.background is None:
self.background = blurred_img
self.background = (1 - self.background_update_rate) * self.background + self.background_update_rate * blurred_img
diff = blurred_img - self.background
diff_abs = np.abs(diff)
binary_image = diff_abs > self.threshold
""" 2.2.1 Opening und Closing """
binary_image = self._221_OpeningClosing(binary_image)
""" 2.3 Canny-Edge und Bildmodifizierung """
canny_edges = self._231_CannyEdge(resized_img_gray)
canny_edges = canny_edges * binary_image
canny_edges = cv2.resize(canny_edges, (int(w), int(h)), interpolation=cv2.INTER_NEAREST)
return canny_edges
""" Reacts on mouse callbacks """
def mouse_callback(self, event, x, y, flags, param):
if event == cv2.EVENT_LBUTTONUP:
print("A Mouse click happend! at position", x, y)
def _plotNoise(self, img, name:str):
height, width = np.array(img.shape[:2])
centY = (height / 2).astype(int)
centX = (width / 2).astype(int)
cutOut = 5
tmpImg = deepcopy(img)
tmpImg = tmpImg[centY - cutOut:centY + cutOut, centX - cutOut:centX + cutOut, :]
outSize = 500
tmpImg = cv2.resize(tmpImg, (outSize, outSize), interpolation=cv2.INTER_NEAREST)
cv2.imshow(name, tmpImg)
cv2.waitKey(1)
def _211_Rauschreduktion(self, img):
"""
Hier steht Ihr Code zu Aufgabe 2.1.1 (Rauschunterdrückung)
- Implementierung Mittelwertbildung über N Frames
"""
return img
def _212_HistogrammSpreizung(self, img):
"""
Hier steht Ihr Code zu Aufgabe 2.1.2 (Histogrammspreizung)
- Transformation HSV
- Histogrammspreizung berechnen
- Transformation BGR
"""
return img
def _221_OpeningClosing(self, binary_image):
"""
Hier steht Ihr Code zu Aufgabe 2.2.1 (Opening and Closing)
- Implementieren Sie das Closing
- Speichern Sie das aktuelle Bild vor und nach der Funktion beim Mausklick
"""
return binary_image
def _231_CannyEdge (self, img_gray):
"""
Hier steht Ihr Code zu Aufgabe 2.3.1 (Manuelle Canny Edge Implementierung)
- Glättung
- Gradienten berechnen
- Nicht-Maximum Unterdrückung
- Hysterese Unterdrückung
"""
""" 1. Glättung """
""" 2. Gradienten berechnen """
""" 3. Nicht-Maximum Unterdrückung """
""" 4. Hysterese Unterdrückung """
#parameter:
thresh1 = 50
thresh2 = 100
return np.ones_like(img_gray) * 255 # hier eigenes Edge-Bild ausgeben

21
CV-App/algorithms/spin.py Normal file
View File

@@ -0,0 +1,21 @@
import cv2
import numpy as np
from . import Algorithm
class Spin(Algorithm):
""" Rotates an image """
def __init__(self):
self.current_angle = 0 # between 0 and 2 pi
self.anlge_per_image = 360 / 100
def process(self, img):
self.current_angle = (self.current_angle + self.anlge_per_image) % 360
w, h = img.shape[1], img.shape[0]
image_center = (w / 2, h / 2)
rot_mat = cv2.getRotationMatrix2D(image_center, self.current_angle, 1.0)
img = cv2.warpAffine(img, rot_mat, (w, h), flags=cv2.INTER_LINEAR)
return img

View File

@@ -0,0 +1,30 @@
import cv2
from . import Algorithm
class TutorialAlgorithm(Algorithm):
""" Writes the RGB values of an pixel to the output image """
def __init__(self):
""" Init reference point with None value """
def process(self, img):
"""
Reads out the RGB values of the reference point and writes it to the output image
"""
if self.pos is not None:
pixel = img[self.pos[1], self.pos[0]]
text = "x:%s y:%s R:%s G:%s B:%s" % (self.pos[0], self.pos[1], pixel[2], pixel[1], pixel[0])
else:
text = "Click on the image!"
font, org, font_scale, color, thickness = cv2.FONT_HERSHEY_SIMPLEX, (50, 50), 1, (0, 0, 0), 2
image = cv2.putText(img, text, org, font, font_scale, color, thickness, cv2.LINE_AA)
return image
def mouse_callback(self, event, x, y, flags, param):
""" Selects a new reference position"""
if event == cv2.EVENT_LBUTTONUP:
# Store x and y to the member value self.pos
pass

View File

@@ -0,0 +1,31 @@
import cv2
from . import Algorithm
class TutorialAlgorithm(Algorithm):
""" Writes the RGB values of an pixel to the output image """
def __init__(self):
""" Init reference point with None value """
self.pos = None
def process(self, img):
"""
Reads out the RGB values of the reference point and writes it to the output image
"""
if self.pos is not None:
pixel = img[self.pos[1], self.pos[0]]
text = "x:%s y:%s R:%s G:%s B:%s" % (self.pos[0], self.pos[1], pixel[2], pixel[1], pixel[0])
else:
text = "Click on the image!"
font, org, font_scale, color, thickness = cv2.FONT_HERSHEY_SIMPLEX, (50, 50), 1, (0, 0, 0), 2
image = cv2.putText(img, text, org, font, font_scale, color, thickness, cv2.LINE_AA)
return image
def mouse_callback(self, event, x, y, flags, param):
""" Selects a new reference position"""
if event == cv2.EVENT_LBUTTONUP:
self.pos = (x, y)

View File

@@ -0,0 +1,31 @@
import cv2
import numpy as np
from . import Algorithm
class WhiteBalancing(Algorithm):
""" White Balancing """
def __init__(self):
""" Define Reference RGB values to (255, 255, 255) """
self.max_b, self.max_g, self.max_r = 255, 255, 255
self.last_image = None
def process(self, img):
""" Performs white balancing based on the reference RGB values """
self.last_image = img
img = img.astype(np.float32)
img[:, :, 0] = np.clip(img[:, :, 0], 0, self.max_b) * 255 / max(1, self.max_b)
img[:, :, 1] = np.clip(img[:, :, 1], 0, self.max_g) * 255 / max(1, self.max_g)
img[:, :, 2] = np.clip(img[:, :, 2], 0, self.max_r) * 255 / max(1, self.max_r)
img = img.astype(np.uint8)
return img
def mouse_callback(self, event, x, y, flags, param):
""" Selects new reference RGB values, if left mouse button is clicked and self.last_image is defined """
if self.last_image is None:
return
if event == cv2.EVENT_LBUTTONUP:
self.max_b, self.max_g, self.max_r = \
self.last_image[y, x, 0], self.last_image[y, x, 1], self.last_image[y, x, 2]

BIN
CV-App/data/cv1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 251 KiB

BIN
CV-App/data/cv2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 KiB

BIN
CV-App/data/cv3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 193 KiB

91
CV-App/data/labelmap.txt Normal file
View File

@@ -0,0 +1,91 @@
???
person
bicycle
car
motorcycle
airplane
bus
train
truck
boat
traffic light
fire hydrant
???
stop sign
parking meter
bench
bird
cat
dog
horse
sheep
cow
elephant
bear
zebra
giraffe
???
backpack
umbrella
???
???
handbag
tie
suitcase
frisbee
skis
snowboard
sports ball
kite
baseball bat
baseball glove
skateboard
surfboard
tennis racket
bottle
???
wine glass
cup
fork
knife
spoon
bowl
banana
apple
sandwich
orange
broccoli
carrot
hot dog
pizza
donut
cake
chair
couch
potted plant
bed
???
dining table
???
???
toilet
???
tv
laptop
mouse
remote
keyboard
cell phone
microwave
oven
toaster
sink
refrigerator
???
book
clock
vase
scissors
teddy bear
hair drier
toothbrush

BIN
CV-App/data/tutorial1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 334 KiB

BIN
CV-App/data/tutorial2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 340 KiB

BIN
CV-App/data/tutorial3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 343 KiB

117
CV-App/exercise.md Normal file
View File

@@ -0,0 +1,117 @@
# Übung: CV-Application
Um diese Übung lösen zu können, lesen Sie das [README.md](README.md).
In dieser Übung vollenden Sie ein Skript, mit dem ein bestimmtes Pixel eines Bildes ausgewählt und dessen RGB-Werte angezeigt
werden können. Für diese Übung werden die Dateien [algorithms/tutorial_algorithm.py](./algorithms/tutorial_algorithm.py) und
[algorithms/\_\_init\_\_.py](./algorithms/__init__.py) modifiziert. Die Musterlösung für die Übung findet sich in der Datei
[algorithms/tutorial_algorithm_solution.py](./algorithms/tutorial_algorithm_solution.py).
## a) Einbinden des Algorithmus
Öffnen Sie die Datei [algorithms/\_\_init\_\_.py](./algorithms/__init__.py). Der Inhalt wird ungefähr wie der folgende
Code-Snippet aussehen:
```python
class Algorithm:
""" An abstract class to create custom algorithms """
def process(self, img):
""" Processes the input image"""
return img
def mouse_callback(self, event, x, y, flags, param):
""" Reacts on mouse callbacks """
return
''' Import algorithms to use'''
from .image_to_gray import ImageToGray
from .image_to_hue import ImageToHue
from .motion_detector import MotionDetector
from .white_balancing import WhiteBalancing
from .spin import Spin
from .segmentation_tracker import SegmentationTracker
''' Link Algorithms to keys '''
algorithms = dict()
algorithms["0"] = Algorithm
algorithms["1"] = ImageToGray
algorithms["2"] = ImageToHue
algorithms["3"] = MotionDetector
algorithms["4"] = WhiteBalancing
algorithms["5"] = Spin
algorithms["6"] = SegmentationTracker
```
Ihre Aufgabe ist es nun, den Algorithmus ``TutorialAlgorithm`` zu importieren und der Taste **7** zuzuordnen.
## b) Vervollständigen des Scripts
Öffnen Sie das Skript [algorithms/tutorial_algorithm.py](./algorithms/tutorial_algorithm.py). Der Inhalt der Datei ähnelt dem
folgenden Code-Snippet:
```python
import cv2
from . import Algorithm
class TutorialAlgorithm(Algorithm):
""" Writes the RGB values of an pixel to the output image """
def __init__(self):
""" Init reference point with None value """
### 1) INSERT self.pos ###
def process(self, img):
"""
Reads out the RGB values of the reference point and writes it to the output image
"""
if self.pos is not None:
pixel = img[self.pos[1], self.pos[0]]
text = "x:%s y:%s R:%s G:%s B:%s" % (self.pos[0], self.pos[1], pixel[2], pixel[1], pixel[0])
else:
text = "Click on the image!"
font, org, font_scale, color, thickness = cv2.FONT_HERSHEY_SIMPLEX, (50, 50), 1, (0, 0, 0), 2
image = cv2.putText(img, text, org, font, font_scale, color, thickness, cv2.LINE_AA)
return image
def mouse_callback(self, event, x, y, flags, param):
""" Selects a new reference position"""
if event == cv2.EVENT_LBUTTONUP:
# Store x and y to the member value self.pos
### 2) UPDATE self.pos ###
```
Zu sehen ist eine Klasse Namens ``TutorialAlgorithm``, welche aus der Klasse ``Algorithm`` abgeleitet wurde. Zu sehen sind
die drei vordefinierten Funktionen ``__init__(self)``, ``process(self, img)`` und
``def mouse_callback(self, event, x, y, flags, param)`` (siehe [README.md](README.md))
Die Funktion ``process(self, img)`` ist bereits fertig implementiert und schreibt einen Text auf das Eingangsbild. Sie
greift auf die Variable ``self.pos`` zu, die bisher noch nicht definiert ist.
Definieren Sie die Member-Variable ``self.pos`` in der Funktion ``__init__(self)`` und weisen Sie ihr den initialen Wert
*None* zu. Dafür vorgesehen ist die Zeile mit dem Inhalt ```### 1) INSERT self.pos ###```.
Nun soll die Variable bei jedem Mausklick mit einem neuen Wert überschrieben werden. Speichern Sie bei jedem Mausklick
die x und y Werte als Tupel ``(x, y)`` in die Variable ``self.None``. Dafür vorgesehen ist die Zeile mit dem Inhalt
``### 2) UPDATE self.pos ###``.
Das Programm ist nun einsatzbereit.
## c) Ausführen des Skripts
Führen Sie das Skript `main.py` aus diesem Verzeichnis mit dem Befehl
```bash
python main.py --mode=screen
```
aus. Nach dem das Skript gestartet wurde, drücken Sie die Taste **7**, um Ihren Algorithmus zu aktivieren. Klicken Sie
dann mit der Maustaste auf eine beliebige Stelle des Bildes. Nach dem Klick sollten die RGB-Werte der gewählten Position
ausgegeben werden.
Die folgenden Abbildungen visualisieren einen beispielhaften Output aus dem Skript.
Nach Start des Programms | Nach Betätigen der Taste **7** | Nach dem Klick auf das Bild
:-------------------------:|:-------------------------:|:-------------------------:
![](./data/tutorial1.png) | ![](./data/tutorial2.png) | ![](./data/tutorial3.png)

131
CV-App/main.py Normal file
View File

@@ -0,0 +1,131 @@
"""
Main file for starting the CV-Application
More infos in the README.md file
"""
import argparse
import cv2
import platform
import numpy as np
import datetime
DEFAULT_CAMERA = 0
DEFAULT_MODE = "screen" # "screen", "virtual_cam"
DEFAULT_VIDEO = "DEFAULT VIDEO TO SHOW"
WINDOW_NAME = "Output"
FRAMERATE = 30
'''
The following code is to setup the framework
'''
print("=== INITIALIZE FRAMEWORK === ")
# Read input arguments
parser = argparse.ArgumentParser(description='CV-App to demonstrate basic CV algorithms in a usefull application')
parser.add_argument(
'--camera', type=int, default=DEFAULT_CAMERA, help='The camera to be opened by the app')
parser.add_argument(
'--mode', type=str, default=DEFAULT_MODE, help="Either 'virtual_cam' for camera emulation or 'screen' for testing")
parser.add_argument(
'--video', type=str, default=DEFAULT_VIDEO, help="The video to use if no camera is available")
args = parser.parse_args()
# Check if arguments are valid
available_cameras = list()
print("Availlable cameras:")
for i in range(3):
temp = cv2.VideoCapture(i)
is_opened = temp.isOpened()
if is_opened:
available_cameras.append(i)
print(" camera with id", i)
temp.release()
assert args.mode in ["screen", "virtual_cam"], "Wrong mode selected! '%s' is not existing!" % args.mode
assert args.camera in available_cameras or args.camera == -1, "Wrong cam selected! '%s' is not existing!" % args.camera
# Get current OS and import camera emulator software (skip if mode=='screen' is used)
current_os = platform.system()
print("Working on ", current_os)
if args.mode == "screen":
def show(img):
cv2.imshow(WINDOW_NAME, img)
elif current_os == "Darwin":
raise NotImplementedError
elif current_os == "Linux":
raise NotImplementedError
elif current_os == "Windows":
import pyvirtualcam
cam = None
def show(img):
global cam
if cam is None:
h, w, c = img.shape
cam = pyvirtualcam.Camera(width=w, height=h, fps=20)
if len(img.shape) == 2:
img = np.stack([img, img, img], axis=2)
cv2.imshow(WINDOW_NAME, img)
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
cam.send(img)
cam.sleep_until_next_frame()
else:
raise Exception("OS %s not known!" % current_os)
# Define CV algorithms you want to use in the application
from algorithms import algorithms
current_algorithm_id = sorted(algorithms.keys())[0]
current_algorithm = algorithms[current_algorithm_id]()
cv2.namedWindow(WINDOW_NAME)
cv2.setMouseCallback(WINDOW_NAME, current_algorithm.mouse_callback)
print("=== FINISHED INITIALIZING FRAMEWORK === \n\n")
'''
Following code runs the processing loop
'''
print("=== RUN PROCESSING LOOP === ")
input_source = args.camera if args.camera != -1 else args.video
print("Using input source", input_source)
cap = cv2.VideoCapture(input_source)
last_read = datetime.datetime.now()
auto_focus = True
auto_exposure = True
while True:
# Measure time to last read out to avoid to fast readout in videos
if datetime.datetime.now() - last_read < datetime.timedelta(milliseconds=int(1000 / FRAMERATE)):
continue
last_read = datetime.datetime.now()
# Read, process and show image
ret, img = cap.read()
if not ret and type(input_source) == str:
cap = cv2.VideoCapture(input_source)
ret, img = cap.read()
img = current_algorithm.process(img)
show(img)
# Check if a new
key = cv2.waitKey(1)
if key == -1:
continue
elif key == 27:
cap.release()
break
elif chr(key) in algorithms.keys():
current_algorithm_id = chr(key)
current_algorithm = algorithms[current_algorithm_id]()
print("Set algorithm to %s selected by key '%s'" % (type(current_algorithm), chr(key)))
cv2.setMouseCallback(WINDOW_NAME, current_algorithm.mouse_callback)
elif chr(key) == "e" and type(input_source) == int:
auto_exposure = not auto_exposure
print("Set auto exposure to", int(auto_exposure))
cap.set(propId=cv2.CAP_PROP_AUTO_EXPOSURE, value=int(auto_exposure))
elif chr(key) == "f" and type(input_source) == int:
auto_focus = not auto_focus
print("Set auto focus to", int(auto_focus))
cap.set(propId=cv2.CAP_PROP_AUTOFOCUS, value=int(auto_focus))
print("=== FINISHED PROCESSING LOOP AND STOP APPLICATION === ")