Benutzer-Werkzeuge

Webseiten-Werkzeuge


Seitenleiste

ss16:dirigieren_protokolle

Protokolle Sitzungen

26. Mai 2016: erste Koordinierung

  • Materialsichtung
  • Kurze Einführung in die Fourier-Analysis durch Stefan und erhalten weiterführender Texte.
  • Installation des Programmes opencv: Erstellen eines Programmes, welches auf die Webcam zugreift um Videos aufzunehmen.
  • Erste Versuche mit einer Erweiterung des Programmes um Gesichter zu erkennen und die Videos auch zu speichern.

2. Juni 2016: Gesichtserkennung und weitere Analyse-Verfahren

Ziele

  • Haarcascaden zum funktionieren bringen
  • Haarcascaden verstehen
  • Mathematik hinter der Bewegungsanalyse verstehen
  • openCV auf allen Computern installieren
  • tubcloud einrichten
  • Video speichern

Protokoll

Nach der Festlegung der Ziele für die heutige Laborsitzung, richteten wir zunächst endlich die tubcloud ein und sorgten dafür, dass openCV auf allen PCs installiert ist. Dies hatte zuvor zu ein paar Problemen geführt, die aber behoben werden konnten. Anschließend teilten wir die Arbeit auf: Jette beschäftigte sich mit der Fourier-Analysis, Robin übernahm die Haarcascaden, Max schaute sich das Videoaufnahmeprogramm nocheinmal genauer an und Henriette versuchte herauszufinden, wie ein mit openCV aufgenommenes Video gespeichert werden kann.

Die Haarcascaden, welche zur Gesichtserkennung verwendet werden, konnten schnell zum Laufen gebracht werden. Problem war zuvor gewesen, dass die Datein beschädigt waren. Stefan stellte uns daraufhin seine vollständigen Haarcascade-Dateien zur Verfügung. Mit diesen funktionierte das Programm zur Gesichteserkennung problemlos, weshalb wir ein wenig mit diesem herumprobierten. Durch eine Eläuterung von Stefan zum Ende des Labors wurde uns dann auch klarer wie genau Haarcascade eigentlich funktioniert.

Mithilfe des folgenden Codes wird ein Video mit der Webcam erzeugt und Gesichter erkannt:

from __future__ import division
from scipy import misc, ndimage
from mpl_toolkits.mplot3d import Axes3D
import math as m
import random
import time
import matplotlib.pyplot as plt
import numpy as np
import cv2
import sys
 
cascPath = sys.argv[1]
faceCascade = cv2.CascadeClassifier(cascPath)
 
cap = cv2.VideoCapture(0)
 
while(True):
    # Capture frame-by-frame
    ret, frame = cap.read()
 
    # Our operations on the frame come here
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
 
    faces = faceCascade.detectMultiScale(
        gray,
        scaleFactor=1.1,
        minNeighbors=5,
        minSize=(30, 30),
        flags=cv2.cv.CV_HAAR_SCALE_IMAGE
    )
 
    # Draw a rectangle around the faces
    for (x, y, w, h) in faces:
        cv2.rectangle(frame, (x, y), (x+w, y+h), (0, 255, 0), 2)
 
 
    # Display the resulting frame
    cv2.imshow('frame',frame)
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break
 
# When everything done, release the capture
cap.release()
cv2.destroyAllWindows()

Die Lösung des Videospeicherungsproblem dauerte hingegen etwas länger. Mit unserem ersten Code wurde zwar eine Videodatei erstellt, jedoch war diese leer und lies sich nicht durch die gängigen Videoplayer öffnen. Nach weiterer Recherche über fourcc und codec meinten wir die Lösung gefunden zu haben, aber immer noch wurde keine Videodatei erstellt. Das Speichern des Videos konnte schließlich durch eine Erweiterung des Codes um eine Zeile erreicht werden. Auch einigten wir uns auf den codec FFMpeg („FMP4“), da dieser auf den meisten Rechnern vorhanden ist.

Hier der Code:

cap = cv2.VideoCapture(0)
 
# Define the codec and create VideoWriter object
# Syntax Videowriterobjekt: cv2.cv.CV_FOURCC([filename, fourcc, fps, frameSize[, isColor]]) fps = frames per second
#speichert video mit dateinamen output in laufendem ordner
 
fourcc = cv2.cv.CV_FOURCC('F','M','P','4') #legt codec des videos fest
out = cv2.VideoWriter('output.avi', fourcc, 24.0, (640,480))
 
while(cap.isOpened()):
    ret, frame = cap.read() #ret= returnvalue
    if ret==True:
 
        cv2.imshow('frame',frame)
        out.write(frame)
 
        if cv2.waitKey(1) & 0xFF == ord('q'):
            break
    else:
        break
 
# Release everything if job is finished
cap.release()
out.release()
cv2.destroyAllWindows()

Zusätzlich erreichen wir, dass aus einem Video, dass die Kamera aufnimmt, in Echtzeit ein Kantenbild erzeugt wird.

9. Juni: Bewegung- und Objekterkennung

Ziele

  • Recherchieren nach Haarcascaden für Hände
  • Programm zur Bewegungserkennung entwickeln mithilfe des optischen Flusses

Arbeit

Im Zuge der Arbeit haben sich zwei Gruppen gebildet. Eine Untergruppe beschäftigte sich dabei mit der Bewegungserkennung (Jette und Henriette) und die zweite Gruppe (Max und Robin) mit der Erkennung von Kopf und Händen über Haarcascades.

Bewegungserkennung

Schoneinmal ein Ergebnisbild zum optischen Fluss:

Objekterkennung (Kopf und Hände)

Zu Beginn wurde nach entsprechenden Haarcascades im Internet gesucht. Auf Github standen solche Cascaden für Fäuste, Handflächen und Hände zur Verfügung. Nach dem erlernen der rudimentären Benutzung von git konnten diese heruntergeladen und verwendet werden. Zur Erkennung von Gesicht und Händen wurde das Programm weiter genutzt, welches in der letzten Sitzung bereits geschrieben wurde, um Gesichter zu erkennen. Das Programm wurde dementsprechend weiterentwickelt, um sowohl Gesicht, als auch Hände zu erkennen. Das weiteren wurde in das Programm eine Funktion integriert, welche die Koordinaten eines Objektes (z.B. Hand) über die Zeit plotten kann.

Als Ergebnis ist folgender Codeschnipsel zu nennen.

from __future__ import division
from scipy import misc, ndimage
from mpl_toolkits.mplot3d import Axes3D
import math as m
import random
import time
import matplotlib.pyplot as plt
import numpy as np
import cv2
import sys
 
 
faceCascade = cv2.CascadeClassifier("face.xml")
handCascade = cv2.CascadeClassifier("fist.xml")
fi=0
 
cap = cv2.VideoCapture(0)
head=[]
right=[]
left=[]
 
def plotten(liste):
 
	fig = plt.figure()
	ax = fig.add_subplot(111, projection='3d')
 
	x =[]
	y =[]
	z =[]
 
	for i in liste:
		x_an, z_an, y_an = i
		x.append(x_an)
		y.append(y_an)
		z.append(z_an)
 
 
	ax.scatter(x, y, z, c='r', marker='o')
 
	ax.set_xlabel('X-Koordinate')
	ax.set_ylabel('Zeit')
	ax.set_zlabel('Y-Koordinate')
 
	plt.show()
 
 
while(True):
    # Capture frame-by-frame
    ret, frame = cap.read()
 
    # Our operations on the frame come here
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
 
    faces = faceCascade.detectMultiScale(
        gray,
        scaleFactor=1.1,
        minNeighbors=5,
        minSize=(30, 30),
        flags=cv2.cv.CV_HAAR_SCALE_IMAGE
    )
 
    lhand = handCascade.detectMultiScale(
        gray[:,320:],
        scaleFactor=1.1,
        minNeighbors=5,
        minSize=(30, 30),
        flags=cv2.cv.CV_HAAR_SCALE_IMAGE
    )
 
    rhand = handCascade.detectMultiScale(
        gray[:,:320],
        scaleFactor=1.1,
        minNeighbors=5,
        minSize=(30, 30),
        flags=cv2.cv.CV_HAAR_SCALE_IMAGE
    )
 
    if fi%4==0:
		for (x, y, w, h) in faces:
			cv2.rectangle(frame, (x, y), (x+w, y+h), (0, 255, 0), 2)
			xcf=(x+w//2)
			ycf=(y+h//2)
			head.append((xcf,ycf,fi))
    else:
		pass
 
    fi+=1
 
    for (x, y, w, h) in rhand:
		cv2.rectangle(frame, (x, y), (x+w, y+h), (255, 0, 0), 2)
		xch=(x+w//2)
		ych=(y+h//2)
		right.append((xch,ych,fi))
 
    for (x, y, w, h) in lhand:
		x+=320
		cv2.rectangle(frame, (x, y), (x+w, y+h), (0, 0, 255), 2)
		xch=(x+w//2)
		ych=(y+h//2)
		left.append((xch,ych,fi))
 
 
    # Display the resulting frame
    cv2.imshow('frame',frame)
    if cv2.waitKey(1) & 0xFF == ord(' '):
        break
 
plotten(left)
 
# When everything done, release the capture
cap.release()
cv2.destroyAllWindows()

Der erstellte Plot zu einem Objekt sieht zum Beispiel wie folgt aus.

16. Juni: Zwischenpräsentation und ein bisschen programmieren

Ziele

-Verhindern, dass Gesicht und Hand bei der Erkennung verwechselt werden. -Erkennung von rechter und linker Hand -Erkennung einer Hand und nicht nur einer Faust -optischer Fluss Vektor für Bewegungserkennung verwenden

Arbeit

Der erste Teil des Labors bestand heute zunächst aus den Zwischenpräsentationen. Wir zeigten den anderen Gruppen, was unsere bisherigen Ergebnisse sind und führten auch das Programm, welches Gesichter und Hände erkennt und den optischen Fluss vor. Danach erfuhren wir noch, was die anderen Gruppen momentan bearbeiten. Des Weiteren gab es einen kleinen Input von Stefan zu den Themen Debugging und Threads.

In der zweiten Hälfte arbeiteten wir dann weiter an unserem Projekt. Robin machte sich daran die Körpererkennung zu verbessern und Max und Henriette setzten sich an die Bewegungserkennung.

Bewegungserkennung

Zu Beginn legten wir uns darauf fest zunächst eine Bewegung nach oben oder unten zu erkennen und überlegten uns, was die Merkmale für diese Bewegungen sind. Zu Hilfe kam dabei auch das Objekterkennungsprogramm der vergangenen Woche, mit Hilfe dessen wir uns die Koordinaten einer geraden Bewegung nach oben/unten anschauen konnten. Dieses Programm erweiterten wir schließlich auch. Wenn eine Hand erkannt wird, werden jeweils die y-Koordinaten zweier aufeinanderfolgender Frames voneinander abgezogen. Ist diese Differenz positiv, haben wir eine Abwärtsbewegung und der Kasten, um die Hand färbt sich rot. Ist die Differenz hingegen negativ, haben wir eine Bewegung nach unten und der Kasten um die Hand färbt sich grün.

Folgender Code realisiert die Bewegungserkennung

from __future__ import division
from scipy import misc, ndimage
from mpl_toolkits.mplot3d import Axes3D
import math as m
import random
import time
import matplotlib.pyplot as plt
import numpy as np
import cv2
import sys
 
 
handCascade = cv2.CascadeClassifier("fist.xml")
cap = cv2.VideoCapture(0)
i=0
 
while(True):
    # Capture frame-by-frame
    ret, frame = cap.read()
 
    # Our operations on the frame come here
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
 
    hand = handCascade.detectMultiScale(
        gray,
        scaleFactor=1.1,
        minNeighbors=5,
        minSize=(60, 60),
        flags=cv2.cv.CV_HAAR_SCALE_IMAGE
    )
 
    #Bewegungserkennung
    for (x, y, w, h) in hand:
		if i == 0:
			a = y
			i+=1
		else:
			b = y
			#wir ziehen die beiden y-Koordinaten der Handposition voneinander ab
			diff = b-a
			#wenn die Differenz > 0 haben wir eine Abwärtsbewegung, der Kasten wird rot
			if diff > 0:  
				cv2.rectangle(frame, (x, y), (x+w, y+h), (255, 0, 0), 2)
			#wenn die Differenz < 0 haben wir eine Bewegung nach oben, der Kasten wird grün
			else:
				cv2.rectangle(frame, (x, y), (x+w, y+h), (0, 255, 0), 2)
			a = b
 
 
 
    # Display the resulting frame
    cv2.imshow('frame',frame)
    if cv2.waitKey(1) & 0xFF == ord(' '):
        break
 
# When everything done, release the capture
cap.release()
cv2.destroyAllWindows()

Zusätzlich zu unserer ersten eigenen Bewegungserkennung erreichten wir durch Veränderung einiger Parameter eine bessere Erkennung von Hand und Gesichtern und, dass rechte und linke Händer erkannt werden.

Da aufgrund der Präsentationen leider nur wenig Zeit zur eingenen Projektarbeit geblieben war, beschlossen wir uns am Wochenende nocheinmal privat zur Weiterarbeit zu treffen.

19. Juni: Extratermin (Sonntag)

Bei unserem Treffen außerhalb des Labors haben wir ein Programm entwickelt, welches es uns ermöglicht, Objekte bestimmter Farben zu erkennen. Hierbei spielt die Funktion cv2.inRange() eine wesentliche Rolle. Sie hilft uns eine Maske zu erstellen, die allen Werten der Quelle, welche innerhalb festgelegter Werte liegen, ein True zuzuordnen und allen Werten, die außerhalb liegen ein False. Hierbei ist es weitaus einfacher das zu analysierende Bild nicht im BGR-Farbraum zu betrachten, sondern im HSV-Farbraum. Dieser besteht ebenfalls aus 3 Werten, dem Hue, der Saturation und dem Value. Hiermit ist es uns einfacher möglich, die inRange-Funktion zu verwenden. Die verwendeten Werte müssen regelmäßig an die Umgebungsbedingungen angepasst werden, da das Programm sonst nicht anständig funktioniert. Die bitwise_and-Funktion ermöglicht es uns dann letztlich aus dem Originalbild nur die Pixelwerte anzeigen zu lassen, die auf der gleichen Position, wie ein True der Maske liegen. Um die Position des getrackten Objektes in einer Koordinate zu bestimmen nutzen wir noch numpy.mean(), da die Koordinate annähernd immer dem Mittelwert des Arrays der True-Postitionen entpricht (siehe Code). Hierbei spielt die Genauigkeit der zu Beginn gewählten Range-Werte eine nicht zu vernachlässigende Rolle.

# -*- coding: utf-8 -*-
 
from __future__ import division
from scipy import misc, ndimage
import numpy as np
import cv2
 
cap = cv2.VideoCapture(0)
 
while True:
	_, frame = cap.read()	
 
	hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)	
	blur = cv2.blur(hsv,(5,5))	
	lower = np.array([170,120,90])						
	upper = np.array([180,255,255])						
	mask =  cv2.inRange(blur, lower, upper)		
	maskinv = cv2.bitwise_not(mask)						
 
	res = cv2.bitwise_and(frame, frame, mask=mask)	
 
	y, x = np.where(mask==255)	
	if len(x)>150:
		px = np.mean(x)			#mittelwert der x koordinaten	
		px = int(px)
		py = np.mean(y)			#mittelwert der y koordinaten
		py = int(py)
		cv2.circle(frame, (px,py), 6, (255,255,255) , -1)
		cv2.circle(frame, (px,py), 7, (0,0,0) , 2)
	else:
		pass
 
	cv2.imshow("frame", cv2.flip(frame,1))	#frame wird gespiegelt
 
	if cv2.waitKey(1) & 0xFF == ord(' '):
		break	
 
cap.release()
cv2.destroyAllWindows()

Fortschritt außerhalb des Labors

Zwischen dem Letzten und dem entsprechenden Treffen sind einige Weiterentwicklungen zu vermeken. Es kam während letzten Treffens die grobe Idee auf, sich mit einer Art Ableitung der Bewegung zu beschäftigen. Um zu untersuchen, ob sich mit dieser etwas anfangen lässt, wurde ein Programmteil entwickelt, welcher sowohl die X-Koordinatendifferenz, als auch die Y-Koordinatendifferenz zwischen zwei aufeinanderfolgenden Bildern berechnet (die Koordinaten des detektierten Taktstocks) und in einer Liste speichert.

if position > 1: #Ableitungen bestimmen
        stab_x_old, stab_y_old = stab[position-2]
	dx_nach_dt.append(stab_x_old-stab_x)
	dy_nach_dt.append(stab_y_old-stab_y)

Sowohl die dektektierten Koordinaten das Taktstockes, als auch die ermittelten Differenzen werden in Form von Listen an eine Funktion übergeben, welche diese Plotten kann.

def plotten2d_ableitung(daten, art, ableitung):
	ableitung.append(0)
	#daten entpacken
	x_head = []
	y_head = []
	x_stab = []
	y_stab = []
	z = range(len(daten))
 
	for i in daten:
		head_x, head_y, stab_x, stab_y = i
		x_head.append(head_x)
		y_head.append(head_y)
		x_stab.append(stab_x)
		y_stab.append(stab_y)
 
	#verarbeitung der daten
 
	auswahl = [x_head, y_head, x_stab, y_stab]
	auswahl_text = ['Kopf-X','Kopf-Y','Stab-X','Stab-Y']
 
	x1 = auswahl[art]
	x2 = ableitung
 
	y1 = z
	y2 = z
 
	plt.subplot(2, 1, 1)
	plt.plot(y1, x1, 'bo-')
	plt.title('Bewegung und ihre Ableitung im Vergleich ' + auswahl_text[art])
	plt.ylabel('Original-Bewegung ' + auswahl_text[art])
	plt.grid()
 
	plt.subplot(2, 1, 2)
	plt.plot(y2, x2, 'r.-')
	plt.xlabel('Zeit (frame)')
	plt.ylabel('Ableitung '+ auswahl_text[art])
	plt.grid()
 
	plt.show()	

Bei der Untersuchung dieser Graphen fällt auf, dass ein Extremum im Koordinatenplot zu einem Vorzeichenwechsel in der Ableitung führt. Aus der Analysis ist der Zusammenhang in ähnlicher Weise bekannt. Es ist bekannt, dass ein Extremum vorliegen könnte, wenn die erste Ableitung gleich Null ist. In unserem Fall können wir uns jedoch diesea Erkenntnis nicht direkt zu nutze machen, da die Differenz zweier Punkte in der Regel nicht Null wird. Der Dirigent verharrt nicht in seiner Bewegung. Allerdings kann der Vorzeichenwechsel der Steigung durchaus detektiert werden. Diese Überlegungen wurden mit in das nächste Treffen genommen.

Zu erwähnen ist an dieser Stelle noch, dass die Bewegungsrichtung aufgrund der Array-Struktur tatsächlich gespiegelt ist.

23. Juni: erste Analyse des Dirigenten

Ziele

  • Extrema der Dirigierbewegungen erkennen und Signalisieren
  • Die Frequenz aus den Extrema extrahieren
  • Die Frequenz ausgeben

Arbeit

Die Überlegungen zu der Bewegung des Dirigenten wurden in Code umgesetzt. Zu Beginn wurde ein if-Anweisung geschrieben, welche den Vorzeichenwechsel in der Ableitung von - zu + detektiert.

if dx_nach_dt[position-1] > 0 and dx_nach_dt[position-2] <= 0:
    cv2.putText(frame,'Hallo', (head_x, head_y), 1, 10, (255,0,0))

Sollte die Bedingung erfüllt sein würde das Wort 'Hallo' im Video an der Position des Kopfes angezeigt werden. Dies gelang. Der nächste Schritt war den Codeteil um die Bedingungen für die drei anderen Extrema zu erweitern. Würde eine Bedingung erfüllt sein, würde auf der entsprechenden Seite des Bildes ein blauer Kreis erscheinen. Auch dies gelang.

if dx_nach_dt[position-1] > 0 and dx_nach_dt[position-2] <= 0:
	cv2.circle(frame, (x_shape-20,y_shape//2), 10, (255,0,0),10)
 
if dx_nach_dt[position-1] < 0 and dx_nach_dt[position-2] >= 0:
	cv2.circle(frame, (20,y_shape//2), 10, (255,0,0),10)
 
if dy_nach_dt[position-1] > 0 and dy_nach_dt[position-2] <= 0:
	cv2.circle(frame, (x_shape//2,y_shape-20), 10, (255,0,0),10)
 
if dy_nach_dt[position-1] < 0 and dy_nach_dt[position-2] >= 0:
	cv2.circle(frame, (x_shape//2,20), 10, (255,0,0),10)

Der nächste Schritt war nun die Frequenz der Aufkommens eines bestimmten Extremas zu ermittel. Zu diesem Zweck wurde eine horizontale Bewegung gewählt, also in Y-Richtung. Das entsprechende Extremum tritt auf, wenn die Ableitung von - nach + wechselt. Um die Dauer zwischen zwei solcher Extrema zu bestimmen wurde die Systemzeit verwendet. Es wurde also time importiert und eine Liste angelegt, welche die Zeitpunkte enthält, zu denen ein Extremum auftritt. Wenn ein solches Auftritt wird an die Liste der entsprechende Zeitpunkt angehängt. Diese Aktion wurde in den oben aufgeführten Code eingebaut. Ebenfalls wurde sie Variable beat_position angelegt, welche angibt, wie viele solcher Extrema bereits registriert wurden.

if dy_nach_dt[position-1] > 0 and dy_nach_dt[position-2] <= 0:
	cv2.circle(frame, (x_shape//2,y_shape-20), 10, (255,0,0),10)
	beat.append(time())
	beat_position += 1

Um die Schläge Pro Minute (BPM) zu emitteln, welche in der Musik oft als Schnelligkeitsangabe verwendet werden, werden die Zeitpunkte von zwei aufeinanderfolgenden Extrema von einander abgezogen. Das Ergebnis wird dann in eine Frequenz umgewandelt (von der Periodenlänge) und mal 60 genommen, um den gewünschten Wert zu bestimmen.

bpm = (beat[beat_position]-beat[beat_position-1])*60

Diese Variable wurde erst im Terminal zum testen ausgegeben und soll nun auf dem Video erscheinen. Hierzu wurde die bereits am Anfang der Sitzung verwendete Funktion cv2.putText() verwendet und entsprechend modifiziert, dass diese den Wert in der unteren rechten Ecke ausgibt.

cv2.putText(frame, str(int(bpm)), (x_shape-250, y_shape-5), 3, 6, (255,0,0),10)

Dies gelang und es konnten aus dem Dirigat in Y-Richtung die Schläge pro Minute abgeleitet werden. (siehe Bild) Die cv2.flip()-Funktion, welche das Bild spiegelt wurde vor die cv2.putText()-Funktion gesetzt, damit der Wert ungespiegelt ausgegeben wird.

Und nun der gesamte Code zum nachvollziehen und eingliedern der einzelnen Code Teile.

# -*- encoding: utf-8 -*-
# Technische Universität Berlin
# mathematisch-naturwissenschaftliches Labor Mathesis
# die 'Dirigieren'-Gruppe
 
from __future__ import division
from scipy import misc, ndimage
from mpl_toolkits.mplot3d import Axes3D
import math as m
import random
from time import *
import matplotlib.pyplot as plt
import numpy as np
import cv2
import sys
 
def plotten3d_gesamt(daten): #Funktion, welche die Daten plotten kann
 
	fig = plt.figure()
	ax = fig.add_subplot(111, projection='3d')
 
	x_head = []
	y_head = []
	x_stab = []
	y_stab = []
	z = range(len(daten))
 
	for i in daten:
		head_x, head_y, stab_x, stab_y = i
		x_head.append(head_x)
		y_head.append(head_y)
		x_stab.append(stab_x)
		y_stab.append(stab_y)
 
	ax.scatter(x_head, z, y_head, c='b', marker='x')
	ax.scatter(x_stab, z, y_stab, c='g', marker='^')
 
	ax.set_xlabel('X-Koordinate')
	ax.set_ylabel('Zeit')
	ax.set_zlabel('Y-Koordinate')
 
	plt.show()
 
def face_rec(cascade, bild): #Funktion, welch Gesichter erkennt und sich für eines entscheidet
 
	gray = cv2.cvtColor(bild, cv2.COLOR_BGR2GRAY) #Konvertierung nach Grau
 
	faces = cascade.detectMultiScale(
		gray,
		scaleFactor=1.1,
		minNeighbors=5,
		minSize=(60, 60),
		flags=cv2.cv.CV_HAAR_SCALE_IMAGE)
	temp = []
	x_old, y_old, w_old, h_old = head[position-1]
	for i in range(len(faces)):
		x_new, y_new, w_new, h_new = list(faces[i])
		temp.append(np.sqrt(x_old*x_new+y_old*y_new))
	if temp == []:
		comp_position = head[position]
	elif min(temp) < 300:
		comp_position = faces[temp.index(min(temp))]
	else:
		comp_position = head[position]
 
	return comp_position
 
def zeichne_rechteck(frame, position, farbe, dicke=2): #Funktion, welche ein Rechteck zeichnet
	x, y, w, h = position
	cv2.rectangle(frame, (x, y), (x+w, y+h), farbe, dicke)
 
def color_rec(frame): #Funktion, welche eine nach Farbe definierte Stelle ermittelt
 
	#     -> Die Werte können nun wie im Zeichenprogramm (z.B. GIMP) gewählt werden
	hue_low = 95 #0-360
	hue_up  = 120 #0-360
	sat_low = 50  #0-100
	sat_up  = 75 #0-100
	val_low = 30  #0-100
	val_up  = 75  #0-100      -> Hier eingestellt für grünen Stift
 
	hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)						#konvertiert frame in hsv 
	lower = np.array([hue_low/2,255*sat_low/100,255*val_low/100])		#legt untere schranke fest
	upper = np.array([hue_up/2,255*sat_up/100,255*val_up/100])			#legt obere schranke fest
	mask =  cv2.inRange(hsv, lower, upper)								#erstellt maske, welche für werte innerhalb des intervalls
																		#den wert 1 annimmt, sonst 0
	y_werte, x_werte = np.where(mask == 255)							#Es werden die x- und y-Werte ausgelesen, welche ein True (255) bekomen haben
	if len(x_werte) != 0 and len(y_werte) != 0:
		y_mittel = int(np.mean(y_werte))									#Es wird der Mittelwert aus allen y-Werten gebildet
		x_mittel = int(np.mean(x_werte))									#Es wird der Mittelwert aus allen x-Werten gebildet
		position = (x_mittel, y_mittel)										#Die Mittlere Position aller Trues entspricht dem Tupel beider Mittelwerte
 
	else:
		position = (0,0)
 
	if position == (0,0):												#Sollten keine Trues gefunden werden (Gesamtmittelwert = (0,0))
		x_shape, y_shape, _ = frame.shape								#Es werden die Bildmaße ausgelesen
		position = (x_shape//2,y_shape//2)								#Als Position wird der Bildmittelpunkt gewählt
 
	return position														#Ergebnis wird zurückgegeben
 
def zeichne_kreis(frame, position): #Funktion, welche einen Kreis zeichnet
	cv2.circle(frame,position, 25, (0,0,255), 4)   
 
def plotten2d_ableitung(daten, art, ableitung):
	ableitung.append(0)
	#daten entpacken
	x_head = []
	y_head = []
	x_stab = []
	y_stab = []
	z = range(len(daten))
 
	for i in daten:
		head_x, head_y, stab_x, stab_y = i
		x_head.append(head_x)
		y_head.append(head_y)
		x_stab.append(stab_x)
		y_stab.append(stab_y)
 
	#verarbeitung der daten
 
	auswahl = [x_head, y_head, x_stab, y_stab]
	auswahl_text = ['Kopf-X','Kopf-Y','Stab-X','Stab-Y']
 
	x1 = auswahl[art]
	x2 = ableitung
 
	y1 = z
	y2 = z
 
	plt.subplot(2, 1, 1)
	plt.plot(y1, x1, 'bo-')
	plt.title('Bewegung und ihre Ableitung im Vergleich ' + auswahl_text[art])
	plt.ylabel('Original-Bewegung ' + auswahl_text[art])
	plt.grid()
 
	plt.subplot(2, 1, 2)
	plt.plot(y2, x2, 'r.-')
	plt.xlabel('Zeit (frame)')
	plt.ylabel('Ableitung '+ auswahl_text[art])
	plt.grid()
	plt.savefig("dirigieren_plot1")
	plt.show()	
 
faceCascade = cv2.CascadeClassifier("face.xml") #Cascade fürs Gesicht wird geladen
 
global position #Variable, welche die Frames durchläuft
position = 0
 
cap = cv2.VideoCapture(0) #Angeschlossene Kamera wird geöffnet
 
ret, frame = cap.read() #Erster Frame wird abgerufen
y_shape, x_shape, z_shape = frame.shape #Videomaße für den Kopfentwicklungspunkt werden ausgelesen
 
global head #Liste der Kopfpositionen
head=[(x_shape//2,0,0,0)] #Entwicklungspunkt für den Kopf
 
global stab #Liste der Taktstockpositionen
stab = []
 
global daten #Liste der wichtigsten Daten
daten = []
 
dx_nach_dt = [0] #Liste für Ableitungen
 
dy_nach_dt = [0] #Liste für Ableitungen
 
beat = [1] #Liste für Zeitpunkte
beat_position = 0 #Extremazähler
 
while(True):
	ret, frame = cap.read() #Holt Frame
 
	head.append(face_rec(faceCascade,frame)) #Ermittelte Kopfposition wird registriert (mit Hoehe und Breite)
	zeichne_rechteck(frame,head[position],(255,0,0),2) #Rechteck wird an aktueller Kopfposition gezeichnet
 
	stab.append(color_rec(frame)) #Ermittelte Taktstockposition wird registriert
	zeichne_kreis(frame,stab[position]) #Kreis wird an aktueller Taktstockposition gezeichnet
 
	head_x, head_y,_,_ = head[position] #die 'wichtigen' Daten (die aktuelle Position) werden aus der Kopfposition extrahiert
	stab_x, stab_y = stab[position-1] #die aktuellen Daten werden aus der Taktstockposition extrahiert
	daten.append((head_x, head_y, stab_x, stab_y)) #die extrahierten Daten werden zur weiteren verarbeitung gespeichert
 
	if position > 1: #Ableitungen bestimmen
		stab_x_old, stab_y_old = stab[position-2]
		dx_nach_dt.append(stab_x_old-stab_x)
		dy_nach_dt.append(stab_y_old-stab_y)
 
		if dx_nach_dt[position-1] > 0 and dx_nach_dt[position-2] <= 0: #Auf Extremum Testen
			cv2.circle(frame, (x_shape-20,y_shape//2), 10, (255,0,0),10) #Entsprechenden Kreis ausgeben
 
		if dx_nach_dt[position-1] < 0 and dx_nach_dt[position-2] >= 0:
			cv2.circle(frame, (20,y_shape//2), 10, (255,0,0),10)
 
		if dy_nach_dt[position-1] > 0 and dy_nach_dt[position-2] <= 0:
			cv2.circle(frame, (x_shape//2,y_shape-20), 10, (255,0,0),10)
			beat.append(time()) #Zeitpunkt registrieren
			beat_position += 1 #Extremazähler erhöhen
 
		if dy_nach_dt[position-1] < 0 and dy_nach_dt[position-2] >= 0:
			cv2.circle(frame, (x_shape//2,20), 10, (255,0,0),10)
 
		frame = cv2.flip(frame,1)
 
		bpm = (beat[beat_position]-beat[beat_position-1])*60 #Schläge pro Minute ermitteln
		cv2.putText(frame, str(int(bpm)), (x_shape-250, y_shape-5), 3, 6, (255,0,0),10)
 
	position +=1 #Framenummer wird um 1 erhöht
	cv2.imshow('frame',frame) #Ergebnis wird angezeigt
	if cv2.waitKey(1) & 0xFF == ord('q'): #'q' zum Beenden drücken
		break
 
plotten3d_gesamt(daten) #die Daten werden geplottet
plotten2d_ableitung(daten, 2, dx_nach_dt)
plotten2d_ableitung(daten, 3, dy_nach_dt)
 
cap.release()
cv2.destroyAllWindows()
#THE ENDE

30.06. Weiter gehts

Ziele

  • Oberziel: Takterkennung, notwendige Schritte hierfür:
    • Verbindung x- und y-Plot (bzw. von zwei Plots im allgemeinen)
    • Schwellenwerte für Richtungsänderungen festlegen
    • Plotanalyse erweitern
    • Charakteristische Piks in Taktdirigierbewegung finden
  • Kalibrierungsprogramm, sodass Erkennung des Dirigierstabes auf verschiedene Lichtverhältnisse angepasst werden kann
  • Erstes Mal verbinden der Dirigierbewegung mit Tonänderung

Arbeit

Für die Programmierarbeit teilten wir uns wieder in zwei Teilgruppen auf. Leider stellten sich diese Mal die technischen Probleme als größte Herausforderung heraus, sodass ein Teil der Gruppe leider aufgrund von fehlenden Netzkabeln, nicht funktionierender Webcam und nicht installiertem CV2Programm auf den Laborrechnern relativ wenig arbeiten konnte.

Außerhalb des Labors

Nach der letzten Laborsitzung hat unser Programm einen großen Fortschritt erziehlt. Robin hat sich zu Hause hingesetzt und einige tolle Erweiterungen hinzugefügt. So wird zu Beginn jetzt eine Kalibrierung des Dirigentenstabes vorgenommen,

07.07. Erfassung weiterer Dirigierparameter

Ziele

  • Robins geniales Programm verstehen
  • Dynamik und Tempo erkennen
  • Dirigiermaßstäbe setzten
  • erkannte Dirigierbewegungen mit Musik verbinden
  • weitere Taktarten erkennen

14.07. Musik? Musik!

Ziele

  • Dirigieren mit Musik verbinden. Hierfür ist nötig:
    • MIDI-Dateien verstehen
    • Schnittstelle definieren
  • Verbesserung des Ablesens des Tempos aus der Frequenz der Dirigierbewegungen
  • Vorbereitung der Präsentation fürs Wissenschaftsfenster am 18.07.

Protokoll

Nach der Festlegung der Zeile teilten wir uns in Gruppen auf. Max nahm sich der Tempoerkennung an. Der Rest, Jette, Robin und Henriette, beschäftigten sich mit den MIDI-Dateien. Als Quelle für unsere Musikdateien fanden wir relativ schnell die Seite www.midiworld.com, welche MIDI-Dateien bekannter Musikstücke gratis zum download zur Verfügung stellt. Im folgenden versuchten wir mehr über den Aufbau und die Manipulation von MIDI-Dateien herauszufinden. Leider findet an hierzur nur sehr wenige Informationen online. Da am Montag die Präsentation unseres Projektes im Wissenschaftsfenster bevor stand, begannen Robin und Henriette schließlich mit der Ausarbeitung der Präsentation. Jette schrieb weiter am Programm und erreichte, dass dieses nun die einzelnen Taktschläge von eins bis vier zählen kann.

18.07. Projektpräsentation im Wissenschaftsfenster

Am Montag war im Wissenschaftsfenster die Präsentation der Projekte aus den Laboren. Robin, Max und Henriette stellten unser Projekt „Das Orchester ist Programm“ den anderen MINTgrünlern vor. In der Präsentation stellten wir die Entstehung unseres Programmes dar von unseren ersten Versuchen bis zur aktuellsten Version. Dabei kam die Präsentation und unsere Idee gut an. Nur dem Triport mussten wir uns in der Endabstimmung geschlagen geben und bekamen den zweiten Platz in der Abstimmung über das beste Projekt.

21.07. endlich Musik!

Heute war wieder eine eher technische Sitzung. Stefan hatte uns ein Programm zur Verfügung gestellt, mit Hilfe dessen man MIDI-Dateien abspielen und verändern kann. Jedoch musste hierfür zunächst auf allen Rechnern die notwendigen Pythonpakete geladen werden. Erforderlich waren pyaudio, pygame, python midi und music21. Leider verlief die Installation wie meist nicht s reibungslos und brauchte Zeit. Aber jetzt können wir mit allen Laptops Midi-Dateien abspielen und auch bearbeiten. Ab der nächsten Sitzung kommen wir also unserem Ziel näher: Das Anpassen der Musik nach den Dirigierbewegungen.

Der Block in der Vorlesungsfreien Zeit

31.08. Tag 1

01.09. Tag 2

02.09. Tag 3

Heute war nun der letzte Tag des Labors. Zu Beginn des Tages stellten wir fest, das die gestrigen Erweiterungen im Bereich der Umrechnung von der Dirigierfrequenz (Beats per Minute) in die Dauer der Miditicks und deren Weiterverarbeitung in ein regelmäßiges Signal zur Ansteuerung der Musik. Die Ansteuerung funktionierte nun ebenfalls. Probleme sind augetreten im Bereich der Beendigung von Stücken während des Stücks unter Windows.

Max hat die Benutzeroberfläche entsprechend angepasst, so dass diese deutlich beutzerfreundlicher ist und besser aussieht.

 Neue Benutzeroberfläche

Zusätzlich zur Optimierung der Ringe und der Ausgabe von Frequenz, Taktart und Taktposition ist nun auch ein Hinweismenü integriert, welches bei Berührung einer Schaltfläche mit dem Taktstock erscheint. Die Ringe werden in diesem Fall ausgeblendet.

Neues Informationsmenü

Henriette hat unter anderem verschiedene Midi-Stücke für diverse Taktarten herausgesucht und es wurde eine Funktion geschrieben, welche automatisch per Zufall ein Stück zur entprechenden Taktart auswählt. Dies funzt soweit. Die letze Version des Programms innerhalb der offiziellen Projektarbeit ist im folgenden zu sehen.:

# -*- coding: utf-8 -*-
 
from __future__ import division
from scipy import misc, ndimage
from mpl_toolkits.mplot3d import Axes3D
import math as m
import random
import time
import matplotlib.pyplot as plt
import numpy as np
import cv2
import sys
from copy import deepcopy
import pickle
import midi
import fluidsynth
import threading
import os
 
 
#=====Object from Stefan=====#
 
def test1():
	fs = fluidsynth.Synth() 
	fs.start(driver="alsa")
 
	sfid = fs.sfload("FluidR3_GM.sf2") 
	fs.program_select(0, sfid, 0, 0)
 
	fs.noteon(0, 60, 50) 
	fs.noteon(0, 67, 30) 
	fs.noteon(0, 76, 30)
 
	time.sleep(1.0)
 
	fs.noteoff(0, 60) 
	fs.noteoff(0, 67) 
	fs.noteoff(0, 76)
 
	time.sleep(1.0)
 
	fs.delete() 
 
class Player(object):
 
	def __init__(self):
		self.d=[]
		self.fs = fluidsynth.Synth() 
		self.fs.start(driver="alsa")
 
		self.sfid = self.fs.sfload("FluidR3_GM.sf2") 
		self.fs.program_select(0, self.sfid, 0, 0)
 
		self.tick_duration = 0.01
 
 
	def load(self, filename):
		'''lädt ein Midi-File mit Namen filename.'''
		with open(filename,'rb') as f:
			self.d=midi.fileio.read_midifile(f)
		self.d.make_ticks_abs()
 
	def play(self):
		'''spielt die Datei in Echtzeit ab, die zugrundeliegende
		Zeiteinheit self.tick_duration lässt sich während des Abspielens
		ändern.'''
		self.stop = False
		for voice in self.d:
			voice_thread = threading.Thread(target=self.play_voice, args=(voice,))
			voice_thread.start()
 
	def stopit(self):
		self.stop = True
 
	def play_voice(self,voice):
		voice.sort(key=lambda e: -e.tick)
 
		tick_now = 0
 
		time_now = time.time()
 
		while len(voice)>0 and not self.stop :
			event=voice.pop()
			while event.tick>tick_now:
				#print event
				n_ticks= max(int((time.time()-time_now)//self.tick_duration)+1,event.tick-tick_now)
				#print n_ticks, n_ticks*self.tick_duration-time.time()+time_now
				time.sleep(n_ticks*self.tick_duration-time.time()+time_now)
				tick_now +=n_ticks
				time_now=time.time()
 
			self.send(event)	
 
	def send(self, event):
		'''Diese Methode kommuniziert mit dem Synthesizer. Bisher sind
		nur drei Ereignistypen implementiert.'''
		if type(event) is midi.NoteOnEvent:
			self.fs.noteon(event.channel,event.data[0],event.data[1])
		elif type(event) is midi.NoteOffEvent:
			self.fs.noteoff(event.channel, event.data[0])
		elif type(event) is midi.ProgramChangeEvent:
			self.fs.program_change(event.channel, event.data[0])
		elif type(event) is midi.ControlChangeEvent:
			self.fs.cc(event.channel, event.data[0],event.data[1])
 
#=====Functions=====#
 
 
def waehle_stueck(taktart, lager_verzeichnis):
	if taktart == "4_4":
		stuecke = os.listdir(lager_verzeichnis + "/4_4")
	elif taktart == "2_2":	
		stuecke = os.listdir(lager_verzeichnis + "/2_2")
	elif taktart == "3_4":
		stuecke = os.listdir(lager_verzeichnis + "/3_4")
	else:
		print "Ungueltige Taktart"
	auswahl = random.choice(stuecke)
	return "/"+ lager_verzeichnis +  "/" + taktart + "/" + auswahl
 
def detect_color(frame): #Funktion, welche eine nach Farbe definierte Stelle ermittelt
 
	hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)   #konvertiert frame in hsv
	lower = np.array([hue_min, sat_min, val_min])  #legt untere schranke fest
	upper = np.array([hue_max, sat_max, val_max])  #legt obere schranke fest
 
	#Die Wert aus den Schranken werden aus globalen Variablen gezogen
 
	mask =  cv2.inRange(hsv, lower, upper)								#erstellt maske, welche für werte innerhalb des intervalls
																		#den wert 1 annimmt, sonst 0
	y_werte, x_werte = np.where(mask == 255)							#Es werden die x- und y-Werte ausgelesen, welche ein True (255) bekomen haben
	if len(x_werte) > 20:												###es reicht eine bedingung zu erfüllen + schwellenwert
		y_mittel = int(np.mean(y_werte))								#Es wird der Mittelwert aus allen y-Werten gebildet
		x_mittel = int(np.mean(x_werte))								#Es wird der Mittelwert aus allen x-Werten gebildet
		position = (x_mittel, y_mittel)									#Die Mittlere Position aller Trues entspricht dem Tupel beider Mittelwerte
 
	else:																###if-bedingung unnötig, mittelpunkt erhält man durch tausch von x und y
		y_shape, x_shape, _ = frame.shape								#Es werden die Bildmaße ausgelesen
		position = (int(x_shape//2),int(y_shape//2))					#Als Position wird der Bildmittelpunkt gewählt
 
	return position														#Ergebnis wird zurückgegeben
 
def get_values(cap):
	while(True): 
		_, frame = cap.read()
		y_shape, x_shape, _ = frame.shape
		cv2.circle(frame,(x_shape//2, y_shape//2),25,(0,0,255),4)
		frame = cv2.flip(frame,1) #Spiegelung des frames an der Horizontalen
		cv2.putText(frame, "Bereit? [B]", (200,450), 2, 1, (255,255,255), 0)
		cv2.imshow('frame', frame)
		if cv2.waitKey(1) & 0xFF == ord('b'): #Signal, dass in Position, über das Drücken von k
			break
		if cv2.waitKey(1) & 0xFF == ord(' '): 
			cap.release()
			sys.exit()
 
	ret, frame = cap.read() #frame ohne Kreis wird geholt
	hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV) #Konvertierung in hsv
	radius = 25 #Radius des zu untersuchenden Kreises
	kreisliste = [] #diese Liste soll alle Pixel enthalten, die innerhalb des Kreises liegen
	for i in range(x_shape): #das Bild wird pixelweise durchgegangen. Wenn der Pixel im Bild liegt, wir dieser registriert
		for j in range(y_shape):
			if np.sqrt((x_shape//2-i)**2+(y_shape//2-j)**2) < radius: #Hier Satz des Pythagoras zur Entfernungsbestimmung Pixel--Kreismittelpunkt
				kreisliste.append(hsv[j][i])
	npkreisliste = np.array(kreisliste) #Konvertierung in Array zur weiteren Verarbeitung
 
	hue = npkreisliste[:,0] #Array mit allen hue-Werten wird angelegt
	sat = npkreisliste[:,1] #Array mit allen sat-Werten wird angelegt
	val = npkreisliste[:,2] #Array mit allen val-Werten wird angelegt
 
		#Berechung der Grenzen erfolgt, wie folgt. Zuerst wird der Mittelwert aller Werte einer Eigenschaft gebildet.
		#Als nächstes wird jeweils die Standartabweichung ermittelt.
		#Die untere Schranke ist dann einfach der Mittelwert - die Standartabweichun
		#Die obere Schranke ist einfach der Mittelwert + die Standartabweichung.
 
		#Die Ergebnisse werden zur direkten Weiterverwendung in globale Variablen geschrieben.
		#Zur verwendung nach einem Programmneustart werden sie in eine datei ausgelagert.
 
	global hue_min #globale Variable wird angelegt
	hue_min = int(np.mean(hue) - np.std(hue)) #Wert wird ermittelt
	global hue_max
	hue_max = int(np.mean(hue) + np.std(hue))
	global sat_min
	sat_min = int(np.mean(sat) - np.std(sat))
	global sat_max
	sat_max = int(np.mean(sat) + np.std(sat))
	global val_min
	val_min = int(np.mean(val) - np.std(val))
	global val_max
	val_max = int(np.mean(val) + np.std(val))
 
	sicherung = (hue_min,hue_max,sat_min,sat_max,val_min,val_max) #erzeuge Datensatztupel zur Abspeicherung für Pickle
	output = open('hsv_werte.pkl', 'w') #die Ausgabedatei wird vorbereitet
	pickle.dump(sicherung, output) #die Daten werden geschrieben
	output.close() #der Output wird geschlossen
 
def schwellenwerte_einlesen():
	global hue_min #die Schwellenwerte werden als global definiert
	global hue_max
	global sat_min
	global sat_max
	global val_min
	global val_max
 
	try: #es wird versucht / festgestellt, ob es bereits eine Datei mit gespeicherten Schwellenwerten gibt
		f = open("hsv_werte.pkl") #falls ja wird diese geoeffnet
		sicherung = pickle.load(f)
		hue_min, hue_max, sat_min, sat_max, val_min, val_max = sicherung #und die Daten entpackt
 
	except: #falls nein, werden sehr komische Werte festgelegt, damit der Benutzter das Programm kalibriert
		val_max = 1
		val_min = 0
		sat_max = 1
		sat_min = 0
		hue_max = 1
		hue_min = 0
 
def detect_gesture(zu_untersuchen, figur):
	figurlaenge = len(figur)
 
	zu_untersuchen_kurz = [] #Diese Liste soll die letzten dirigierten Vektoren enthalten, die wichtig sind
	for i in range(figurlaenge,0,-1):	#von index -1 bis index 0
		zu_untersuchen_kurz.append(zu_untersuchen[-i]) #letzten Eintraege werden ermittelt
	zu_untersuchen_kurz = np.array(zu_untersuchen_kurz) #zur weiteren Verarbeitung Konvertierung in Array
 
	figurensammlung = [] #verschiedene Kombinationen werden angelegt, schwierig sich das ohne zeichnung vorzustellen. Bsp.: aus [1,2,3] wird [[1,2,3],[2,3,1],[3,2,1]]
	tmp = figur
	for i in range(figurlaenge):
		tmp.append(tmp[0])
		del(tmp[0])
		anhaengen = deepcopy(tmp)
		figurensammlung.append(anhaengen)
	figurensammlung = np.array(figurensammlung) #zur weiteren Verarbeitung Konvertierung in Array
 
	for i in range(figurlaenge): #Der Vergleich findet statt
		if np.array_equiv(figurensammlung[i],zu_untersuchen_kurz): #elementweises Vergleichen von letztem Dirigat und verschiedenen Figur-Kombinationen
			return True #falls Figur gefunden
 
	return False #falls Figur nicht gefunden
 
def detect_bpm(l):
	global a
	global b
	global bpm
	if l > 2:
		b = time.clock()
		bpm = 60/(b-a)
		a = b
	else:
		a = time.clock()
 
def get_vec(old, new):
	if new == None:
		return None
	elif old == new:
		return None
	elif old == 1:
		if new == 2:
			return (1,0)
		elif new == 3:
			return (1,-1)
		elif new == 4:
			return (0,-1)
	elif old == 2:
		if new == 1:
			return (-1,0)
		elif new == 3:
			return  (0,-1)
		elif new == 4:
			return (-1,-1)
	elif old == 3:
		if new == 1:
			return (-1,1)
		elif new == 2:
			return (0,1)
		elif new == 4: 
			return (-1,0)
	elif old == 4:
		if new == 1:
			return (0,1)
		elif new == 2:
			return (1,1)
		elif new == 3:
			return (1,0)
 
def get_visual(pos):
	if new == 1:
		cv2.circle(frame, c1, cthresh, color, 3)
		cv2.circle(clean, c1, cthresh, color, 3)
	if new == 2:
		cv2.circle(frame, c2, cthresh, color, 3)
		cv2.circle(clean, c2, cthresh, color, 3)
	if new == 3:
		cv2.circle(frame, c3, cthresh, color, 3)
		cv2.circle(clean, c3, cthresh, color, 3)
	if new == 4:
		cv2.circle(frame, c4, cthresh, color, 3)
		cv2.circle(clean, c4, cthresh, color, 3)
 
	cv2.circle(frame, c1, cthresh, color, 3)
	cv2.circle(frame, c2, cthresh, color, 3)
	cv2.circle(frame, c3, cthresh, color, 3)
	cv2.circle(frame, c4, cthresh, color, 3)
	cv2.circle(frame, pos, 4, (0,0,255), -1)	
	cv2.circle(clean, pos, 4, (0,0,255), -1)
	cv2.circle(frame, (640,480), cthresh, (0,255,255), -1)	
 
def get_length(a,b):			#berechnet länge eines vektors von 2 punkten
	ax, ay = a
	bx, by = b
	cx = ax - bx
	cy = ay - by
	l = np.sqrt(cx**2+cy**2)
	return l
 
def in_circle(pos):
 
	vec1 = get_length(c1, pos)		#sammlung der längen der vektoren von den mittelpunkten zur position
	vec2 = get_length(c2, pos)
	vec3 = get_length(c3, pos)
	vec4 = get_length(c4, pos)
	vecmenu = get_length(cmenu, pos)
 
	lengths = [cthresh+1, vec1, vec2, vec3, vec4, vecmenu]
 
	for x in lengths:
		if x < cthresh:
			ret = lengths.index(x)
			if ret == 5:
				return "menu"
			return ret
 
def detect_gesture(zu_untersuchen, figur): #diese Funktion versucht Figuren im Dirigat zu erkennen. Dafuer bekommt sie die Figurvektoren in richtiger Reihenfolge, sowie die Liste mit den dirigierten Vektoren uebergeben
	figurlaenge = len(figur)
 
	zu_untersuchen_kurz = [] #Diese Liste soll die letzten dirigierten Vektoren enthalten, die wichtig sind
	for i in range(figurlaenge,0,-1):
		zu_untersuchen_kurz.append(zu_untersuchen[-i]) #letzten Eintraege werden ermittelt
	zu_untersuchen_kurz = np.array(zu_untersuchen_kurz) #zur weiteren Verarbeitung Konvertierung in Array
 
	figurensammlung = [] #verschiedene Kombinationen werden angelegt, schwierig sich das ohne zeichnung vorzustellen. Bsp.: aus [1,2,3] wird [[1,2,3],[2,3,1],[3,2,1]]
	tmp = figur
	for i in range(figurlaenge):
		tmp.append(tmp[0])
		del(tmp[0])
		anhaengen = deepcopy(tmp)
		figurensammlung.append(anhaengen)
	figurensammlung = np.array(figurensammlung) #zur weiteren Verarbeitung Konvertierung in Array
 
	for i in range(figurlaenge): #Der Vergleich findet statt
		if np.array_equiv(figurensammlung[i],zu_untersuchen_kurz): #elementweises Vergleichen von letztem Dirigat und verschiedenen Figur-Kombinationen
			return True #falls Figur gefunden
 
	return False #falls Figur nicht gefunden
 
def beat_number(vec,figure):
	a,b = vec
 
	if figure == 'triangleA':
		if vec == (0,-1):
			return 1
		if vec == (-1,0):
			return 2 
		else:
			return 3
	if figure == 'triangleB':
		if vec == (1,0):
			return 1
		if vec == (0,1):
			return 2 
		else:
			return 3
	if figure == 'triangleC':
		if vec == (-1,0):
			return 1
		if vec == (0,1):
			return 2 
		else:
			return 3
	if figure == 'triangleD':
		if vec == (0,-1):
			return 1
		if vec == (1,0):
			return 2 
		else:
			return 3
 
	if figure == 'rectangleCCW':
		if vec == (0,-1):
			return 1
		if vec == (1,0):
			return 2
		if vec == (0,1):
			return 3
		if vec == (-1,0):
			return 4
	if figure == 'rectangleCW':
		if vec == (0,-1):
			return 4
		if vec == (1,0):
			return 3
		if vec == (0,1):
			return 2
		if vec == (-1,0):
			return 1
 
	if figure == 'line':
		if vec == (0,-1):
			return 1
		else:
			return 2
	else:
		return False	
 
def check_gestures():
	global figure
	if len(veclist) > 4:
		if detect_gesture(veclist, rectangleCW):
				figure = "rectangleCW"
				counter()
		if detect_gesture(veclist, rectangleCCW):
				figure = "rectangleCCW"
				counter()
		if detect_gesture(veclist, triangleA):
				figure = "triangleA"
				counter()
		if detect_gesture(veclist, triangleB):
				figure = "triangleB"
				counter()
		if detect_gesture(veclist, triangleC):
				figure = "triangleC"
				counter()
		if detect_gesture(veclist, triangleD):
				figure = "triangleD"
				counter()
		if detect_gesture(veclist, line):
				figure = "line"
				counter()
 
def counter():
	f = len(figure)				#bescheuertes verfahren, nötig da detect_gesture die länge der figurenliste ändert
	if f > 10:
		r = 4 
	else:
		r = int(np.sqrt(f))
 
	cv2.putText(mirror, str(beat_number(veclist[-1],figure)) + "/" + str(r), (40,40), 2, 1, (255,255,255), 0)
	return r
 
def bpm_mean(bpm):
	if bpm > 40:
		bpmlist.append(bpm)
		if len(bpmlist) < 10:
			npbpm = np.array(bpmlist)
			mean = int(np.mean(npbpm))/10
			mean = int(mean)*20
 
		else:
			rel = bpmlist[-10:]
			npbpm = np.array(rel)
			mean = int(np.mean(npbpm))/10
			mean = int(mean)*10
		return mean
	return bpm
 
def tick_laenge_ermitteln(bpm):
	if (60/ bpm)/192 > 0:				#1 tick 0,1s# 1 viertel 19,2s#
		l=(60/ bpm)/192
		return l
 
def menu_visual(cap):
	while(True): 
		_, frame = cap.read()
		position = detect_color(frame)
 
		new = in_circle(position)
 
		if new != "menu":
			break
 
		frame = cv2.flip(frame,1) 
 
		#===Menü===#
		cv2.circle(frame, (0,480), cthresh, (0,255,255), -1)	
		cv2.putText(frame, "INFO", (200,80), 2, 1, (255,255,255), 0)
		cv2.putText(frame, "Kalibrieren [K]", (200,130), 2, 1, (255,255,255), 0)
		cv2.putText(frame, "Pause/Start [P]", (200,170  ), 2, 1, (255,255,255), 0)
		cv2.putText(frame, "Beenden [Leer]", (200,210  ), 2, 1, (255,255,255), 0)
		#===Ende===#
 
		cv2.imshow('frame', frame)
 
		if cv2.waitKey(1) & 0xFF == ord(' '): 
			cap.release()
			sys.exit()
 
def waehle_stueck(taktart, lager_verzeichnis):
	if taktart == 4:
		stuecke = os.listdir(lager_verzeichnis + "/4_4")
		art = "4_4"
	elif taktart == 2:	
		stuecke = os.listdir(lager_verzeichnis + "/2_2")
		art = "2_2"
	elif taktart == 3:
		stuecke = os.listdir(lager_verzeichnis + "/3_4")
		art = "3_4"
	else:
		print "Ungueltige Taktart"
	auswahl = random.choice(stuecke)
	return  lager_verzeichnis +  "/" + art + "/" + auswahl
 
#=======gestures=======#
 
rectangleCW = [(1,0),(0,-1),(-1,0),(0,1)]
rectangleCCW = [(1,0),(0,1),(-1,0),(0,-1)]  
triangleA = [(1,1),(0,-1),(-1,0)]
triangleB = [(-1,-1),(1,0),(0,1)]
triangleC = [(1,-1),(-1,0),(0,1)]
triangleD = [(-1,1),(0,-1),(1,0)]
line = [(0,1),(0,-1)]
gestures = [rectangleCW, rectangleCCW, triangleA, triangleB, triangleC, triangleD, line]
 
#=======circles=======#
 
c1 = ((320+120),(240-120))
c3 = ((320-120),(240+120))
c2 = ((320-120),(240-120))
c4 = ((320+120),(240+120))
cmenu = (640,480)
color = (10,10,255)
 
#=======variables=======#
 
global fnr, bpm, new
 
fnr = 0							
bpm = 0
cthresh = 95			
ref = (320,240)
old = 0	
new = 5
pp = 0
last_state = False
 
#=======lists=======#
 
veclist = [None]						#liste beinhaltet richtungsänderungen
bpmlist = []
 
#=======program=======#
 
cap = cv2.VideoCapture(0)
schwellenwerte_einlesen()
 
while True:
	_, frame = cap.read()	
	clean = np.copy(frame)
 
	position = detect_color(frame)
 
	get_visual(position)
 
	new = in_circle(position)
 
	if new == "menu":
		menu_visual(cap)
		continue
 
	vec = get_vec(old, new)
 
	if vec != None and vec != veclist[-1]:
		veclist.append(vec)
		detect_bpm(len(veclist))
 
	if new != None and new != old:
		old = new
 
	bpm = bpm_mean(bpm)
 
	frame = cv2.addWeighted(frame, 0.5, clean, 0.5, 0)
	mirror = cv2.flip(frame,1)
 
	check_gestures()
 
	cv2.putText(mirror, str(int(bpm)), (560,40), 2, 1, (255,255,255), 0)
 
	cv2.imshow("frame", mirror)
	fnr += 1
 
	if fnr == 100:
		p = Player()
		p.load(waehle_stueck(counter(), "../Midi"))
		p.play()
	if fnr > 100:
		if bpm > 0:
			p.tick_duration = tick_laenge_ermitteln(bpm)
 
 
	if cv2.waitKey(1) & 0xFF == ord('k'):
		get_values(cap)
	"""if cv2.waitKey(1) & 0xFF == ord('p') and fnr > 100 and last_state == False:
		if pp == 0:
			p.stopit()
			pp = 1
		if pp == 1:
			p.play()
			pp = 0
		last_state = True
	else:
		last_state = False"""
 
	if cv2.waitKey(1) & 0xFF == ord(' '):
		if fnr > 100:
			p.stopit()
		print "herunterfahren"
		break	
 
cap.release()
cv2.destroyAllWindows()
ss16/dirigieren_protokolle.txt · Zuletzt geändert: 2016/09/02 17:29 von robinkrueger