home    zurück
letzte Änderung am 02.03.2016

Weitere Erkenntnisse


Nach ein paar weiteren Tests und Spielerein habe ich entdeckt, dass Numpy mit vorzeichenlosen Arrays deutlich langsamer arbeitet, als mit vorzeichenbehafteten Arrays.
Nachtrag: Dies hat sich allerdings als Trugschluss herausgestellt. Es ist vielmehr so, dass das Setzen von allen oder keinen Elementen sehr viel schneller abläuft, als das Setzen von einigen oder vielen Elementen. Siehe unten.

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

  img1=cam.getImage().convert('L')
  time.sleep(0.1)
  img2=cam.getImage().convert('L')

  t1=time.clock()
  arr1=np.asarray(img1, np.int8)    # vorzeichenbehaftet
  arr2=np.asarray(img2, np.int8)
  t2=time.clock()
  diff=np.absolute(arr1-arr2)
  t3=time.clock()
  diff[diff<32]=0
  t4=time.clock()
  print("%2.3f ms"%((t2-t1)*1000),
        "   %2.3f ms"%((t3-t2)*1000),
        "   %2.3f ms"%((t4-t3)*1000),
        "   %2.3f ms"%((t4-t1)*1000),
        " -> ", type(diff[0, 0]))
  cam.saveImage(np.uint8(diff), "diff1")
 
  t1=time.clock()
  arr1=np.asarray(img1, np.uint8)   # vorzeichenlos
  arr2=np.asarray(img2, np.uint8)
  t2=time.clock()
  diff=arr1-arr2
  t3=time.clock()
  diff[diff<32]=0
  diff[diff>224]=0
  t4=time.clock()
  print("%2.3f ms"%((t2-t1)*1000),
        "   %2.3f ms"%((t3-t2)*1000),
        "   %2.3f ms"%((t4-t3)*1000),
        "   %2.3f ms"%((t4-t1)*1000),
        " -> ", type(diff[0, 0]))
  cam.saveImage(diff, "diff2")
 
  t1=time.clock()
  arr1=np.asarray(img1)              # aus input-data abgeleitet
  arr2=np.asarray(img2)
  t2=time.clock()
  diff=arr1-arr2
  t3=time.clock()
  diff[diff<32]=0
  diff[diff>224]=0
  t4=time.clock()
  print("%2.3f ms"%((t2-t1)*1000),
        "   %2.3f ms"%((t3-t2)*1000),
        "   %2.3f ms"%((t4-t3)*1000),
        "   %2.3f ms"%((t4-t1)*1000),
        " -> ", type(diff[0, 0]))
  cam.saveImage(diff, "diff3")

Liefert dann sowas:
dede@i5:~/bastelein/ipcam> ./tst14.py
0.418 ms    0.723 ms    0.521 ms    1.662 ms  ->  <class 'numpy.int8'>
0.265 ms    0.067 ms    4.141 ms    4.473 ms  ->  <class 'numpy.uint8'>
0.263 ms    0.061 ms    4.132 ms    4.456 ms  ->  <class 'numpy.uint8'>
dede@i5:~/bastelein/ipcam> ./tst14.py
0.424 ms    0.683 ms    0.486 ms    1.593 ms  ->  <class 'numpy.int8'>
0.275 ms    0.069 ms    3.930 ms    4.274 ms  ->  <class 'numpy.uint8'>
0.255 ms    0.062 ms    3.899 ms    4.216 ms  ->  <class 'numpy.uint8'>
dede@i5:~/bastelein/ipcam> ./tst14.py
0.469 ms    0.808 ms    0.579 ms    1.856 ms  ->  <class 'numpy.int8'>
0.335 ms    0.068 ms    4.657 ms    5.060 ms  ->  <class 'numpy.uint8'>
0.276 ms    0.083 ms    4.455 ms    4.814 ms  ->  <class 'numpy.uint8'>
dede@i5:~/bastelein/ipcam> ./tst14.py
0.436 ms    0.703 ms    0.497 ms    1.636 ms  ->  <class 'numpy.int8'>
0.302 ms    0.061 ms    3.924 ms    4.287 ms  ->  <class 'numpy.uint8'>
0.266 ms    0.060 ms    4.006 ms    4.332 ms  ->  <class 'numpy.uint8'>
dede@i5:~/bastelein/ipcam>

Die beiden asarray-Anweisungen brauchen im Mittel also folgende Zeiten:
(0.418+0.424+0.469+0.436)/4=0.43675
(0.265+0.275+0.335+0.302)/4=0.29425
(0.263+0.255+0.276+0.266)/4=0.265
In der Doku zu asarray() heißt es: "By default, the data-type is inferred from the input data."
Offensichtlich sind die Datentypen vom zweiten und dritten Durchlauf identisch (uint8).
Trotzdem läuft der asarray() ohne expliziten type-cast minimal schneller.  ...how auch ever...

Ein deutlicher Unterschied ergibt sich aber bei der Berechnung des Differenzbildes.
Beim vorzeichenbehafteten Array dauert dieser Schritt länger, dafür wird mit np.absolute() direkt die positive Differenz der Bilder bestimmt.
Beim vorzeichenlosen Array macht np.absolute() keinen Sinn und daher muss hier das Problem mit dem Null-Durchgang abgefangen werden. Also wenn das Pixel (x,y) in img1 z.B. den Wert 100 und das Pixel (x,y) in img2 den Wert 110 hat, ist das Ergebnis 100-110=-10. Vorzeichenlos aber (100-110)&0xff=246.

Beim vorzeichenbehafteten Array reicht es, das Array einmal zu durchlaufen und alle Werte <32 auf =0 zu setzen.
Beim vorzeichenlosen Array muss das Array zweimal durchlaufen werden, um den selben Effekt zu erreichen.
Jedoch ist z.B. der Wert 0.521 (im ersten Durchlauf von tst14.py) deutlich kleiner als 4.141/2.
Das ist nur so zu deuten, dass die Anweisung diff[diff<32]=0 deutlich schneller auf vorzeichenbehafteten Arrays operiert.

Bei einer entsprechenden Nachfrage auf stackoverflow kam die Vermutung auf, dass der uint8 intern mit uint64-Funktionen arbeitet.
Nachtrag: Weiterhin wurde erkannt, dass das Setzen mittels diff[diff<32]=0 dann sehr viel schneller läuft, wenn alle Elemente eines Arrays gesetzt werden. Oder wenn keins gesetzt wird. Dazwischen, wenn also einige oder viele Elemente gesetzt werden, wirds deutlich langsamer.
Nach ein paar Iterationen sah mein Test-Script folgendermaßen aus:
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-

import time
import numpy as np

if __name__=="__main__":
  v=[[10, 245], [100, 145]]
  vt=["change all    ", "change nothing"]
  for i in (0, 1):
    for t in [np.int8, np.uint8, np.int, np.uint, np.int32, np.uint32]:
      a=np.empty([480, 640], t)
      for y in range(480):
        for x in range(640):
          a[y, x]=v[i][x&1]  # (50%=10, 50%=245) oder (50%=100, 50%=145)
      t1=time.process_time()
      for l in range(1000):
        b=1*a
        b[b<32]=0
        b[b>224]=0
      t2=time.process_time()
      print("%s %8.2f ms"%(vt[i], (t2-t1)*1000), a.dtype)

Liefert:
change all       327.99 ms int8
change all      3079.23 ms uint8
change all      3367.71 ms int64
change all      3386.04 ms uint64
change all      2847.84 ms int32
change all      2871.66 ms uint32
change nothing  1627.19 ms int8
change nothing   117.88 ms uint8
change nothing   747.36 ms int64
change nothing   747.02 ms uint64
change nothing   296.32 ms int32
change nothing   307.25 ms uint32

Sehr bizarr. Auf einem Array vom Typ int8 sind die Anweisungen zum "Extremwerte löschen" schneller, wenn es viel zu löschen gibt.
Das hieße ja, dass Lesen+Schreiben schneller liefe, als Lesen alleine.
Dieser b<32 liefert wohl eine boolsche Matrix der Größe 640x480.
Und mit b[b<32]=0 wird dann jedes Element von b, wo das entsprechende Element in der boolschen Matrix True ist, auf 0 gesetzt.
Warum das jetzt vielfach langsamer laufen sollte, wenn die boolsche Matrix zu 100% den Wert False enthält, kann ich nicht deuten.
Nachtrag: jetzt schon. Also mit dem Wissen, dass es viel schneller geht, alle oder keine Elemente auf Null zu setzen. Bei int8 werden Zahlen >127 negativ dargestellt. Daher setzt der b[b<32]=0 alles und der b[b>224]=0 nichts. Bei uint8 setzen beide jeweils 50%.

Aber wenn ich das Script
if __name__=="__main__":
  cam=IP_Cam()

  img1=cam.getImage().convert('L')
  time.sleep(0.1)
  img2=cam.getImage().convert('L')

  i=0
  for t in [np.int8, np.uint8, np.int, np.uint, np.int32, np.uint32]:
    arr1=np.asarray(img1, t)
    arr2=np.asarray(img2, t)

    t1=time.process_time()
    for l in range(1000):
      if t in (np.int8, np.int, np.int32):
        diff=np.absolute(arr1-arr2)
      else:
        diff=arr1-arr2
        diff[diff>224]=0
      diff[diff<32]=0
    t2=time.process_time()
    cam.saveImage(np.uint8(diff), "diff%d"%(i,))
    print("%8.2f ms"%((t2-t1)*1000), diff.dtype)
    i+=1
mit zwei Bildern von der Kamera füttere, kommt das hier heraus:
  383.39 ms int8
 1365.53 ms uint8
 1278.36 ms int64
 2317.85 ms uint64
  601.93 ms int32
 1638.93 ms uint32

Das Differenz-Bild für int8 sieht so aus:
int8

Das Differenz-Bild für uint8 sieht so aus:
uint8

Und alle anderen Differenz-Bilder sehen durchgängig so aus:
int / uint >8

Mal sehen....da werde ich mein Script wohl nochmal umbauen....nächstes Wochenende vielleicht.


Jetzt habe ich noch mal drüber geschlafen und dann ist mir ein Fehler aufgefallen.
Bei vorzeichenlosen Arrays liefert der diff=arr1-arr2 Werte über Null und Werte unter Null, die aber zwangsläufig über Null dargestellt werden.
Die unteren positiven Werte werden mit dem diff[diff<32]=0 gelöscht.
Mit dem diff[diff>224]=0 sollen die eigentlich negativen (real aber als hoch positiv dargestellten) Werte, ebenfalls gelöscht werden. Werden sie auch.
Aber evt. sind da auch Werte dabei, die nicht durch den Nulldurchgang entstanden sind.
Es ist zwar in diesem Fall (bei einem Differenz-Bild) relativ unwahrscheinlich, dass viele solcher Werte entstehen, aber es ist zumindest denkbar, dass ein Pixel in Bild_1 den Wert 250 und dasselbe Pixel im Bild_2 den Wert 10 hat. Ergäbe einen Wert von 240 der nicht als -16 (-16&0xff=240) interpretiert, und daher auf Null gesetzt, werden sollte.
Das bedeutet, dass ich bisher nicht nur die geringen Bild-Unterschiede ausgeblendet habe, sondern auch die besonders hohen.
So war das natürlich nicht gedacht. Ein Grund mehr, auf np.int8 umzustellen.

Umstellung auf np.int8 ?