home     zurück
letzte Änderung am 25.02.2016

Kamera-Nachführung bei Bewegungserkennung


Die erste Version des Nachführungs-Scripts war nach wenigen Minuten fertig:

class IP_Cam:
[...]
  def gotoMatrixElement(self, x, y):
    if y<2: dv=self.UP
    else:   dv=self.DOWN
    if x<2: dh=self.LEFT
    else:   dh=self.RIGHT
    if y in (0, 3): tv=0.57   # 1.5 Felder rauf/runter
    else:           tv=0.23   # 0.5 Felder rauf/runter
    if x in (0, 3): th=2.3    # 1.5 Felder links/rechts
    else:           th=0.86   # 0.5 Felder links/rechts

    self.gotoDirection(dv, tv)
    self.gotoDirection(dh, th)

# ###########################################################
# Main
if __name__=="__main__":
  cam=IP_Cam()

  img1=cam.getImageGrayscale()
  while True:
    time.sleep(0.2)
    img2=cam.getImageGrayscale()

    diff=cam.removeNoise(img1-img2, 32)
    s=np.sum(diff)
    if s>500000:
      p_mh, p_mv=findBrightestArea(diff)
      print(p_mh, p_mv)
      cam.gotoMatrixElement(p_mh, p_mv)
      time.sleep(0.2)
      img2=cam.getImageGrayscale()
    img1=img2

Funktioniert!
Und das gewünschte Grusel-Gefühl stellt sich schon ein bischen ein.

Noch hat das Script aber den Nachteil, dass jede Bewegungserkennung zwangsläufig immer zu einer neuen Kamera-Position führt.
Auch dann, wenn sich der Bildausschnitt mit der maximalen Bewegung bereits in der Bildmitte befindet.

Was kann man da tun?
Vielleicht statt einer 4x4-Matrix eine 5x5-Matrix verwenden.
Damit gäbe es ein einzelnes bzw. komplettes Matrix-Element genau in der Bildmitte.
Und 640x480 ist sogar ganzzahlig in eine 5x5-Matrix aufteilbar -> 128x96
Aber ich müsste meine Messung bezüglich der Bewegungs-Dauer zum Anfahren eines Matrix-Elements wiederholen.
Wie lästig. Dann müsste ich ja auch mein Test-Bild neu zeichnen. Mist! Das hätte ich mir auch früher überlegen können.

Ebenfalls denkbar wäre, den findBrightestArea() nicht nur ein Matrix-Element liefern zu lassen.
Würde er stattdessen immer alle Matrix-Elemente mit einer Gewichtung liefern, könnte vielleicht auch irgendwie besser auf mehrere Bereiche mit Bewegung reagiert werden.
Jedoch müsste findBrightestArea() dann immer alle Bild-Bereiche bearbeiten - und nicht nur das hellste Viertel.
Zufälligerweise habe ich ja genau diese Funktion bereits bezüglich ihrer Laufzeit durchgemessen und bin auf lächerliche 1.5 ms gekommen. Da wäre also durchaus noch Luft nach oben. Auch 3 ms Laufzeit sollte kein Problem darstellen.

Weil ich keine Lust darauf habe, die Matrix-Element-Anfahr-Dauer für eine 5x5-Matrix neu zu ermitteln, könnte ich also erstmal eine Funktion sortBrightestAreas() bauen, die vielleicht nicht unbedingt alle Matrix-Elemente liefert, aber zumindest die hellsten drei oder vier.
Dadurch würde gotoMatrixElement() jedoch deutlich komplexer werden müssen.
Mal nachdenken....Die Matrix stellen wir mal so dar:
  a b c d
  e f g h
  i j k l
  m n o p
Würde nun z.B. gewichtete Bewegung in den Elementen [g=100, f=80, k=75] erkannt, könnte daraus eine Koordinate berechnet werden, die irgendwo im linken unteren Eck des g-Elements läge. Das hieße nun aber, dass ich nicht mehr einfach 16 feste Koordinaten anfahren kann. Würden die Dauern linear ansteigen oder hätte ich deutlich mehr Werte, ließen sich die Anfahrts-Dauern zu jedem Punkt interpolieren. Das kann ich aber getrost vergessen. Dafür ist die Kamera einfach zu ungenau.

Höchst edel wäre natürlich ein selbständiges Einmessen der Kamera.
Also die Kamera auf ein statisches (aber uneinheitliches) Bild ausrichten, dieses Bild in eine z.B. 5x5-Matrix zerlegen und die Software dann solange mit Positionierungs-Dauern spielen zu lassen, bis sie die Zeiten kennt, jedes einzelne Matrix-Element in die Bildmitte zu holen.
Statt 5x5 wäre auch 7x5 denkbar. Das ergäbe ein eher quadratisches Matrix-Element mit 91x96 Pixeln. 7x91=637. Es blieben also drei Pixel am Bildrand unberücksichtigt.
Probiere ich allerdings, ein 640x480 Pixel großes Bild mittels np.hsplit(img, 9) in neun Teile zu teilen, gibts eine Fehlermeldung:
    ValueError: array split does not result in an equal division

Also nochmal in der NumPy-Doku stöbern....diesmal gings fix.
slice ist das Schlüsselwort.
a=np.array([[1, 2, 3, 4, 5],[6, 7, 8, 9, 10],[11, 12, 13, 14, 15], [16, 17, 18, 19, 20], [21, 22, 23, 24, 25]])
print(a)
print(a[0:3, 0:3])
print(a[2:4, 2:4])
Liefert:
[[ 1  2  3  4  5]
 [ 6  7  8  9 10]
 [11 12 13 14 15]
 [16 17 18 19 20]
 [21 22 23 24 25]]
[[ 1  2  3]
 [ 6  7  8]
 [11 12 13]]
[[13 14]
 [18 19]]

Sehr schön.
Dann will ich jetzt mal ein Bild in 7x5 Teile zerlegen.
Links lasse ich ein Pixel weg, rechts zwei.
if __name__=="__main__":
  cam=IP_Cam()

  img=cam.getImageGrayscale()

  cx=7  # Anzahl Elemente auf der X-Achse
  cy=5  # Anzahl Elemente auf der Y-Achse
  w=91  # Breite eines Elements
  h=96  # Höhe eines Elements
  print(img.shape)
  for y in range(cy):
    for x in range(cx):
      elem=img[y*h:y*h+h, 1+x*w:1+x*w+w]
      cam.saveImage(elem, "m%d%d"%(x,y))

Liefert:




































Sehr schön soweit - aber offensichtlich wäre dies keine sonderlich gute Ausgangsposition für das Script zum Suchen der Anfahrts-Dauern für Matrix-Elemente: das Fenster ist zu gleichmäßig weiß. Da könnte compareImagePattern() keine eindeutige Position finden. Aber ich könnte temporär z.B. eine Pflanze auf den Drucker stellen.

BTW: Für den compareImagePattern() wäre eine Bild-Größe von 91x96 Pixeln durchaus brauchbar, wenn mit einer 7x8-Matrix gearbeitet wird.
91/7=13 und 96/8=12.
Was mir dabei aber ein bischen Sorgen macht, ist die geringe Größe der Matrix-Elemente - in Beziehung zur [nicht gegebenen] Positionierungs-Genauigkeit der Kamera.
Bei 7x5 Elementen zu je 91x96 Pixeln würde der getImagePattern() bei einer 7x8-Matrix jeweils einen Grauwert für einen Bildbereich von 13x12 Pixeln bilden.
Dies müsste dann eigentlich auch die Auflösung der Kamera-Servos sein. Und das ist wohl ein sehr frommer Wunsch....keine Chance.

Nach einem kurzen Nickerchen ist mir die Idee gekommen, dass ich ja nicht beide Achsen gleichzeitig ermitteln muss.
Es langt völlig, zuerst nur horizontal zu schwenken und die Spalten 2, 1 und 0 zu treffen. Danach dann vertikal die Zeilen 1 und 0.
So mache ich das.

Auto-Justage



Anmerkung (weil es mir gerade einfällt...):
Man mag sich fragen: "warum dokumentiert dieser Hansel das hier eigentlich alles?"
Antwort: es hilft mir, bei meinen Projekten voranzukommen, in dem ich mich quasi selbst zwinge, einmal erlangte Erkenntnisse aufzuschreiben und den Weg dorthin nochmal zu überdenken, um dadurch eine [in sich] halbwegs schlüssige Herleitung für eine Lösung dokumentieren zu können.
Und die soll dann ja auch ordentlich sein - schließlich können es potentiell viele Menschen lesen....und da möchte man sich ja nicht all zu sehr blamieren.