Forum: Digitale Signalverarbeitung / DSP / Machine Learning Kreuzkorrelation und Signallängen


von Christoph a. (chrichri)


Lesenswert?

Hi
ich habe zwei Signale (A und B). Signal B soll in A gefundne werden. 
Dabei gilt A ist länger (also mehr Messwerte) als B.

Dazu verwende ich in C++ die FFT-Library FFTW und berechne die 
Korrelation.

- A und B auf Länge länge(A)+länge(B)-1 bringen ("rechtsseitiges 
zeropadding")
- FFT von A und B bilden
- Beide FFTs elementweise multiplizieren, eines aber komplex konjugiert 
Ergebnis = FFT(A) * conj(FFT(B))
- Inverse FFT des Ergebnis IFFT(Ergebnis)
- Maxima in Inverse suchen. Maximaposition ist die gesuchte Position.

Das funktioniert soweit wie erwartet.

Mein Problem ist aber dass die Länge von A variablen ist (aber stehts 
größer/gleich der Länge von B) und ich so schnell wie möglich sein will.
D.h. ich will, wenn möglich, FFT(B) nur einmalig berechnen. Das Pronlem 
ist ja dass ich mit länge(A)+länge(B)-1 rechne.

Kann ich das irgendwie machen?
Gruß
Christoph

von Andreas S. (andreas) (Admin) Benutzerseite


Lesenswert?

"Schnelle Faltung" mit Overlap-Save oder Overlap-Add 
(http://inst.eecs.berkeley.edu/~ee123/fa11/docs/FastConv.pdf).

Was sind denn typische Längen von A und B?

von Christoph a. (chrichri)


Lesenswert?

Danke.

A typisch 300-1200 messwerte
B typisch 100-300  messwerte

nur so eine größenordnung ;-)

von Florian F. (ultrazauberer)


Lesenswert?

Wie sieht denn der Quellcode dazu aus? Finde ich ziemlich interessant, 
da ich etwas ähnliches vorhabe (Laufzeitmessung).
Vielleicht finde ich noch etwas dazu.

von Christoph a. (chrichri)


Lesenswert?

Also das PDF von Florian konnte ich noch nicht bearbeiten, geschweige 
denn in Code umsetzen. Habe gerade auch andere Probleme an denen ich 
hänge, deswegen muss die Performance-Geschichte erstmal warten.

Meinen bisherigen Code darfst du aber gerne haben ;-)
Es ist aber nur ein Testprogramm! Ich verwende die Library FFTW für die 
Korrelation und die Library OpenCV weil ich später auch mit Bildern 
arbeite. (Deswegen is der Code auch umfangreicher als nötig)

Ich nehme ein Bild X und betrachte es Zeilenweise als Signal X_Zeile.
Dann nehme ich den festen Ausschnitt X_Template aus X_Zeile (z.B. Pixel 
100 bis 600) und korreliere X_Template mit X_Zeile (also z.B. 800 
einzelne Korrelationen bei einem Bild der Höhe 800 px). Für jede 
Korrelation wird das Maximim gesucht und als Ergebnis gespeichert. Ideal 
sollte überall 100 herauskommen (mit den Beipielwerten). (Tut es aber 
nicht)

Anschließend bilde suche ich den Median aller Ergebnisse. Er entspricht 
i.d.R. den gesuchten 100. (Wenn das Template zu ist dann nicht mehr)

Verbesserungswürdig ist die Art der Korrelation da nicht mittelwertfrei 
oder normiert. (Zum testen reicht erstmal die "normale" 
Kreuzkorrelation)

Was ich später nicht haben will ist dass ich jedes mal alle Daten 
(fft-daten etc) allokiere, die Datansätze fülle etc. Das kostet Zeit.
1
#include <stdio.h>
2
#include <stdlib.h>
3
#include <iostream>
4
#include <opencv2/opencv.hpp>
5
#include <fftw3.h>
6
#include <fstream>
7
#include "fftw_samples.h"
8
#include <time.h>
9
10
void fft_1d();
11
int compare(const void * a, const void * b);
12
13
using namespace std;
14
15
int main (int argc, char * const argv[]) {
16
  
17
  fft_1d();
18
  
19
  return 0;
20
}
21
22
void fft_1d(){
23
  
24
  clock_t c_start, c_init, c_fft, c_log, c_tmp;
25
  
26
  int matchloc_should_be = 100+1; //+1 wegen matlab
27
  int width_of_template  = 600;
28
  cv::Mat image = cv::imread("abc.bmp",0);
29
  cv::Mat templ = cv::Mat(image,cv::Rect(matchloc_should_be,0,width_of_template,image.size().height)).clone();
30
  
31
  cv::Mat image_sm;
32
  cv::Mat templ_sm;
33
  pyrDown(image, image_sm);
34
  pyrDown(templ, templ_sm);
35
  imshow("image", image_sm);
36
  imshow("templ", templ_sm);
37
  cvWaitKey(0);
38
  cv::destroyAllWindows();
39
  
40
  printf("Matchloc should be: %d \n", matchloc_should_be);
41
  printf("image - width: %d height: %d \n", image.size().width, image.size().height);
42
  printf("templ - width: %d height: %d \n", templ.size().width, templ.size().height);
43
  
44
  
45
  c_tmp   = clock();
46
  c_start = clock();
47
  
48
  // length of data (both, image and template data must have same length)
49
  int size_w = image.size().width + templ.size().width - 1;
50
  
51
  // number of lines must be the same
52
  int size_h = image.size().height;
53
  
54
  //fftw plans
55
  fftw_plan    plan_forward, plan_backward;
56
  
57
  // Pointer to current data
58
  // fftw_complex    * curr_image_data, *cur_templ_data;
59
  
60
  // new array of pointers for image data
61
  fftw_complex    ** image_data = new fftw_complex*[size_h];
62
  fftw_complex    ** image_fft  = new fftw_complex*[size_h];
63
  
64
  // new array of pointers for template data
65
  fftw_complex    ** templ_data = new fftw_complex*[size_h];
66
  fftw_complex    ** templ_fft  = new fftw_complex*[size_h];
67
  
68
  // new array of pointers for result data
69
  fftw_complex    ** corr_data  = new fftw_complex*[size_h];
70
  fftw_complex    ** ifft_data  = new fftw_complex*[size_h];
71
  
72
  // allocate data for image and template
73
  for (int i = 0; i < size_h; i++) {
74
    image_data[i] = ( fftw_complex* ) fftw_malloc( sizeof( fftw_complex ) * size_w );
75
    image_fft[i]  = ( fftw_complex* ) fftw_malloc( sizeof( fftw_complex ) * size_w );
76
    
77
    templ_data[i] = ( fftw_complex* ) fftw_malloc( sizeof( fftw_complex ) * size_w );
78
    templ_fft[i]  = ( fftw_complex* ) fftw_malloc( sizeof( fftw_complex ) * size_w );
79
    
80
    corr_data[i]  = ( fftw_complex* ) fftw_malloc( sizeof( fftw_complex ) * size_w );
81
    ifft_data[i]  = ( fftw_complex* ) fftw_malloc( sizeof( fftw_complex ) * size_w );
82
  }
83
  
84
  //zero padding image, template and result data
85
  for (int i = 0; i < size_h; i++) {
86
    for (int e = 0; e < size_w ; e++) {
87
      image_data[i][e][0] = 0.0;    // real      part
88
      image_data[i][e][1] = 0.0;    // imaginary  part
89
      
90
      templ_data[i][e][0] = 0.0;    // real      part
91
      templ_data[i][e][1] = 0.0;    // imaginary  part
92
      
93
      corr_data[i][e][0]  = 0.0;    // real      part
94
      ifft_data[i][e][1]  = 0.0;    // imaginary  part
95
    }
96
  }
97
  
98
  // populate image data
99
  for (int i = 0; i < size_h; i++) {
100
    for (int e = 0; e < image.size().width ; e++) {
101
      image_data[i][e][0] = (double)(image.at<uchar>(i,e));    // real      part
102
      //image_data[i][e][1] = 0.0;                // imaginary  part
103
    }
104
  }
105
  
106
  // populate template data
107
  for (int i = 0; i < size_h; i++) {
108
    for (int e = 0; e < templ.size().width ; e++) {      
109
      templ_data[i][e][0] =  (double)(templ.at<uchar>(i,e));;    // real      part
110
      //templ_data[i][e][1] = 0.0;                // imaginary  part
111
    }
112
  }
113
  
114
  c_init = clock() - c_tmp;
115
  c_tmp  = clock();
116
  
117
  // calculate fft of image and template data
118
  for (int i = 0; i < size_h; i++) {
119
    plan_forward  = fftw_plan_dft_1d(size_w, image_data[i], image_fft[i], FFTW_FORWARD, FFTW_ESTIMATE );
120
    fftw_execute( plan_forward );
121
    
122
    plan_forward  = fftw_plan_dft_1d(size_w, templ_data[i], templ_fft[i], FFTW_FORWARD, FFTW_ESTIMATE );
123
    fftw_execute( plan_forward );
124
  }
125
  
126
  //calculate correlation result in frequency domain
127
  double a,b,c,d;
128
  for (int i = 0; i < size_h; i++) {
129
    for (int e = 0; e < size_w; e++) {
130
      a = image_fft[i][e][0];  // real
131
      b = image_fft[i][e][1];  // imaginary
132
      c = templ_fft[i][e][0];  // real
133
      d = templ_fft[i][e][1];  // imaginary
134
      
135
      corr_data[i][e][0] = a*c + b*d;
136
      corr_data[i][e][1] = b*c - a*d;      
137
    }
138
  }
139
  
140
  //inverse transform result
141
  for (int i = 0; i < size_h; i++) {
142
    plan_backward = fftw_plan_dft_1d(size_w, corr_data[i], ifft_data[i], FFTW_BACKWARD, FFTW_ESTIMATE );
143
    fftw_execute( plan_backward );
144
  }
145
  
146
  //find maxima in result
147
  double * max = new double[size_h];
148
  int * maxpos = new int[size_h];
149
  for (int i = 0; i < size_h; i++) {
150
    max[i]    = 0;
151
    maxpos[i] = 0;
152
  }  
153
  for (int i = 0; i < size_h; i++) {
154
    for (int e = 0; e < size_w; e++) {
155
      if(ifft_data[i][e][0] > max[i]) {
156
        max[i]    = ifft_data[i][e][0];
157
        maxpos[i] = e;
158
      }
159
    }
160
  }
161
  
162
  c_fft = clock() - c_tmp;
163
  c_tmp = clock();
164
  
165
  // save to disk for matlab
166
  ofstream myfile;
167
  myfile.open ("log.txt");
168
  for (int e = 0; e < size_w; e++){
169
    for (int i = 0; i < size_h; i++) {
170
      myfile << ifft_data[i][e][0] << " ";
171
    }
172
    myfile << endl;
173
  }
174
  myfile.close();
175
  
176
  myfile.open ("log_corr_cpp.txt");
177
  for (int i = 0; i < size_h; i++){
178
    myfile << maxpos[i] << " " << max[i] << endl;
179
  }
180
  myfile.close();
181
182
  c_log = clock() - c_tmp;
183
  c_tmp = clock();
184
  
185
  // find median of maxima positions
186
  qsort((void*)maxpos, size_h, sizeof(int), compare);
187
  int median = 0;
188
  if(size_w%2){ // uneven
189
    median = maxpos[size_h/2 + 1];
190
  }
191
  else {
192
    median = (maxpos[size_h/2] + maxpos[size_h/2+1])/2;
193
  }
194
  printf("median found at: %d",median);
195
  
196
  // log sorted correlation result
197
  myfile.open ("log_corr_sorted_cpp.txt");
198
  for (int i = 0; i < size_h; i++){
199
    myfile << maxpos[i] << " " << max[i] << endl;
200
  }
201
  myfile.close();
202
  
203
  printf("\ntime: init %f - fft %f - log %f \n", 
204
       (float)c_init/(float)CLOCKS_PER_SEC, 
205
       (float)c_fft/(float)CLOCKS_PER_SEC,
206
       (float)c_log/(float)CLOCKS_PER_SEC);
207
}
208
209
int compare (const void * a, const void * b)
210
{
211
  return ( *(int*)a - *(int*)b );
212
}

von Florian F. (ultrazauberer)


Lesenswert?

Danke für den Code! Scheint ja mehr als reichlich zu sein.

Da wir hier gerade beim Thema sind: Ich möchte ein einfaches Echolot / 
Sonar auf MCU Basis realisieren.

Dazu soll die Korrelation zum Einsatz kommen. Ich bin gerade dabei mich 
in das Thema einzulesen. Jedenfalls hab ich mittlerweile begriffen, dass 
man mittels Kreuzkorrelation die Laufzeit zwischen 2 Signalen bestimmen 
kann.

Gedanklich hab ich folgendes vor:
- Impuls erzeugen und aussenden (erstes Signal ist bekannt)
- Echo empfangen und Signal puffern
- jetzt das erste Signal im zweiten suchen
- Differenz ist dann die doppelte Laufzeit

Das ähnelt ja sehr dem Verfahren von Christoph.

Jetzt habe ich gelesen, dass durch die Korrelation auch bildgebende 
Algorithmen möglich sind, um beispielsweise eine Darstellung wie auf 
einem richtigen Sonar zu haben. Weiß einer wie das funktioniert? Das 
kann ich mir gerade nämlich nicht vorstellen.

von Christoph a. (chrichri)


Lesenswert?

Florian F. schrieb:
> Jetzt habe ich gelesen, dass durch die Korrelation auch bildgebende
> Algorithmen möglich sind, um beispielsweise eine Darstellung wie auf
> einem richtigen Sonar zu haben. Weiß einer wie das funktioniert? Das
> kann ich mir gerade nämlich nicht vorstellen.

Das ist jetzt nur mein Verständnis der Sache ohne großartig Literatur zu 
lesen:
"Bildgebend" ist das Verfahren doch immer dann, wenn du eine Reihe von 
Daten, die du durch dein Verfahren bestimmt hast, interpretierts.

Z.B. Ein UBoot bewegt sich über den Meeresgrund, misst die 
Signallaufzeit zum Grund in festen Intervallen. Über die Laufzeit ist 
die Entfernung bestimmbar (z.B. Schallgeschwindigkeit unter Wasser). 
Somit kannst du ein 1-D Höhenprofil des Grundes (in Fahrtrichtung des 
UBootes) erstellen.

Was ich damit aber sagen will: IMHO ist das "bildgebende Verfahren" nur 
die Verwertung deiner Messdaten und hat erst einmal nichts mit dem 
Messverfahren zu tun.

Das ganze kann man natürlich noch sinnvoll verbessern. Z.B. wäre es bei 
dem UBoot angebracht den Grund in einer gewissen Breite abzutasten. 
Banale Lösung wäre: Einfach mehrer Sensoren parallel anordnen, dann hat 
man maherer 1-D Streifen und kann so ein 2-D Bild erstellen. Aber wieder 
bleibt das Messprinzip unberührt.
Hier wir aber der Nutzen der Korrelation deutlich: Die Sensoren können 
gleichzeitig Arbeiten, sie müssten aber nur jeder ein hinreichend 
unterschiedliches Signal aussenden, welches die anderen in iher 
Signalverarbeitung nicht stört.

von iPod-User (Gast)


Lesenswert?

Christoph abc schrieb:
> Das kostet Zeit.

Das kostet das Scrollen durch deinen Code auch, insbesondere auf 
Mobilgeräten. Ist bei dir die Funktion zum Hochladen von Anhängen 
kaputt?

von Robert K. (Firma: Medizintechnik) (robident)


Lesenswert?

Florian F. schrieb:
> - Echo empfangen und Signal puffern
>
> - jetzt das erste Signal im zweiten suchen

Wie willst du wissen, dass das, was Du empfängts, ein Echo ist und zu 
wissen, was du Puffern musst? Wenn Es das Echo ist, musst Du nicht 
suchen.

Wenn Du suchst, um es erst zu finden, dann musst Du alles aufnehmen und 
mehrfach suchen.

von Florian F. (ultrazauberer)


Lesenswert?

Ich nehme alles auf und lass das Signal samplen. Wenn ich schon 
aufnehme, bevor ich einen Ping losschicke, habe ich bei der Aufnahme am 
Anfang ein ziemlich starkes Signal, was gleich als Muster herhalten kann 
und zur Synchronisation dient.

Danach mache ich eine diskrete, normierte Korrelation über das gesamte 
Signal und suche darin das Muster. Am Anfang werde ich somit ein Signal 
mit einem Korrelationskoeffizienten von 1 erhalten, was mein Muster ist. 
Alle Signale danach sind entweder Rauschen oder das Echo. Das Echo wird 
sicherlich auch irgendwas um die 0,5-0,9 als Korrelationskoeffizienten 
haben.

Wie sollte man es sonst am besten machen?

von Robert K. (Firma: Medizintechnik) (robident)


Lesenswert?

Florian F. schrieb:
> Wie sollte man es sonst am besten machen?

Hört sich plausibel an, so. Ist die Frage, wie engmaschig du suchst, 
also von sample 1 ... sample 1024  und dann sample 2 ... saple 1025 oder 
gröber.

im Frequenzbereich (FFT) müsste es einfacher sein, weil dann die Phase 
rausfällt, wegen / sin/cos.

von A. S. (rava)


Lesenswert?

ich weiß jetzt nicht genau, was du machen möchtest, aber ein matched 
filter sollte doch genau das tun, was du suchst, oder?

der Vorteil ist, er arbeitet im Zeitbereich. Du musst nur eine Faltung 
programmieren: du faltest das gemessene Signal mit dem Signal, das du 
finden möchtest und machst eine Grenzwert- und/oder Spitzendetektierung.


http://en.wikipedia.org/wiki/Matched_filter


ich habe keine Ahnung von Kreuzkorrelation, aber wenn die 
Aufgabenstellung tatsächlich eine "Suche" ist, ist mir nicht klar, wofür 
der Frequenzbereich nötig ist.
das Phasenargument kann ich nicht ganz nachvollziehen, denn die Faltung 
arbeitet ja kontinuierlich. Was also in den Daten ist, wird gefunden.

Oder nicht?

von Harald M. (mare_crisium)


Lesenswert?

Florian,

ich habe so etwas einmal ausprobiert; es funktionierte auch ganz gut 
(siehe letzter Beitrag in diesem Thread: 
Beitrag "Ultraschall Merkmalsvektoren").

Als Signal wird bei diesem Verfahren eine Impulsfolge gesendet, die 
pseudo-zufällig ist, eine sogenanne MLS. Gleichzeitig mit dem Senden 
wird das empfangene Signal digitalisiert und gespeichert; das direkte 
Übersprechen vom Sender auf den Empfänger hat bei diesem speziellen 
Verfahren keinen störenden Einfluss, solange es den Empfänger nicht 
übersteuert. In meinem Versuchsaufbau hatte ich die beiden US-Kapseln 
ungefähr parallel ausgerichtet; das reichte schon aus, um diese 
Bedingung zu erfüllen.

Die MLS haben eine besondere mathematische Eigenschaft, die sich 
trickreich ausnutzen lässt, um das gesendete Signal im empfangenen 
wiederzufinden. Wie und warum das funktioniert, habe ich in dem 
.pdf-Dokument beschrieben, das an dem zitierten Beitrag angehängt ist. 
Die Mathematik dahinter ist etwas ungewohnt, lässt sich aber mit einem 
einfachen ATmega durchführen, der über genügend viel RAM verfügt.

Ciao,

mare_crisium

von Florian F. (ultrazauberer)


Lesenswert?

Danke für das viele Feedback. Ich werde mir auf jeden Fall das PDF mal 
zu Gemüte führen. Den Ansatz, den ich damals beschrieben habe, hat auch 
funktioniert. Ich habe es nicht weiter verkompliziert.

Ich muss nochmal in meinen Unterlagen schauen, wo ich die Formel für die 
Kreuzkorrelation ist. Damit ging es zumindest eine einfache 
Echolotanwendung zu realisieren. Im Grunde habe ich die Formeln 1:1 als 
Programm umgesetzt und erstmal so etwas rumgespielt und getestet. 
Nachdem alles den Erwartungen entsprochen hat, habe ich es dann mit der 
FFT realisiert und damit waren dann wirkliche Leistungssprünge zu 
erreichen. :-)

von Udo S. (urschmitt)


Lesenswert?

Falls ihr euch für Korrelationsfunktionen interessiert kann ich euch das 
Werk von Hänsler ans Herz legen
"Statistische Signale, Grundlagen und Anwendungen"

Ich durfte seine Vorlesung als Pflichtvorlesung hören und war froh 
meinen Schein geschafft zu haben :-)

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.