Quello che si vuole trattare in questo tutorial è un aspetto più tecnico che di Computer Vision. In presenza di un gran numero di immagini, il doversi ricalcolare a ogni esecuzione i dati di interesse (come ad esempio keypoint e descrittori), può portare a una grossa perdita di tempo. Per fortuna OpenCV mette a disposizione uno strumento per scrivere e leggere i nostri dati in file di formato XML o YAML. Nel codice di esempio riportato a fine articolo vedremo uno dei possibili modi per salvare su file YAML e per caricare informazioni da questo. Per l'esempio abbiamo esteso il codice già presentato nell'articolo sulla Feature Detection: lo scopo è quello di calcolare i punti di corner solo la prima volta e caricarli da file durante le esecuzioni successive, senza richiamare la funzione goodFeaturesToTrack() ogni volta. Prima di passare al codice di esempio, diamo un'occhiata alle classi e alle funzioni.
La classe principale per questo tipo di lavoro è FileStorage. Contiene tutti metodi i necessari per scrivere o leggere su file. Perciò se volete salvare informazioni in modo persistente, la prima cosa da fare è creare un'istanza della classe FileStorage con il seguente costruttore:
FileStorage::FileStorage(const string& source, int flags, const string& encoding=string())
dove source indica il file XML o YAML sul quale salvare o caricare le informazioni; con flags indicate se aprite il file indicato da source in lettura o scrittura; encoding indica la codifica del file. Di seguito, l'istruzione per aprire un file in scrittura e associarlo ad un oggetto FileStorage:
FileStorage fs("storage.yml", FileStorage::WRITE);
Dopo questa istruzione, l'oggetto fs farà riferimento al file storage.yml e potrete aggiungerci informazioni utilizzando l'operatore <<
int X = 100; fs << "X" << X;
In questo caso avete aggiunto al file la variabile X che potrete richiamare in seguito utilizzando lo stesso identificatore: la prima "X" è l'identificatore che usate nel file YAML e XML, la seconda X è la variabile intera che avete dichiarato ed inizializzato nella riga precedente.
Quando avete finito di salvare variabili nel file, chiamate la funzione release() per chiudere il file e rilasciare tutti i buffer di memoria.
Per quanto riguarda la lettura da file, semplicemente dopo aver aperto il file in modalità READ fate riferimento al campo che volete caricare per memorizzarne il contenuto in una variabile (il funzionamento è molto simile ad una map C++):
FileStorage fs("storage.yml", FileStorage::READ); int XLoaded; XLoaded = fs["X"];
Veniamo adesso al codice di esempio: come spiegato in precedenza, il codice controlla prima l'esistenza del file storage.yml; se questo esiste, i corner e gli altri dati sono caricati direttamente; altrimenti è creato il file e su questo sono immagazzinate le informazioni.
#include #include <opencv/cv.h> #include <opencv/highgui.h> using namespace std; using namespace cv; int main() { Mat img = imread("lena.bmp", CV_LOAD_IMAGE_GRAYSCALE); int cornerCount; vector corners; double qualityLevel; double minDistance; int blockSize; int useHarris; FileStorage fs("storage.yml", FileStorage::READ); if (fs.getFirstTopLevelNode().empty()) { cornerCount = 100; qualityLevel = 0.01; minDistance = 5; blockSize = 20; useHarris = false; goodFeaturesToTrack(img, corners, cornerCount, qualityLevel, minDistance, noArray(), blockSize, useHarris); FileStorage fs("storage.yml", FileStorage::WRITE); fs.getFirstTopLevelNode().empty(); fs << "cornerCount" << cornerCount; fs << "qualityLevel" << qualityLevel; fs << "minDistance" << minDistance; fs << "blockSize" << blockSize; fs << "corners" << "["; for (int i = 0; i < cornerCount; i++) { fs << "{:" << "x" << corners[i].x << "y" << corners[i].y << "}"; } fs << "]"; fs.release(); } else { cornerCount = (int) fs["cornerCount"]; qualityLevel = (double) fs["qualityLevel"]; minDistance = (double) fs["minDistance"]; blockSize = (int) fs["blockSize"]; FileNode featuresCorners = fs["corners"]; FileNodeIterator it = featuresCorners.begin(); FileNodeIterator it_end = featuresCorners.end(); for (int index = 0; index < cornerCount && it != it_end; ++it, index++) { Point2f point = Point2f((int) (*it)["x"], (int) (*it)["y"]); corners.push_back(point); } fs.release(); } for (int i = 0; i < cornerCount; i++) { float x = corners[i].x; float y = corners[i].y; cout << "x: " << x << ", y: " << y << endl; circle(img, Point(x, y), 5, Scalar(255, 0, 0, 0), 2, 8, 0); } namedWindow("Window", CV_WINDOW_AUTOSIZE); imshow("Window", img); waitKey(0); return 0; }
Immagine utilizzata nel codice:
Una particolarità che salta subito all'occhio sono le righe di codice per la scrittura e la lettura delle coordinate dei corner point. Nel primo caso è creato il campo corners al quale è poi associata una sequenza di coppie x y. Per creare una sequenza avete bisogno di usare le parentesi quadre, mentre per le strutture dati (come una coppia di coordinate in questo caso), usate le parentesi graffe.
Viceversa per caricare i corner point è usato un oggetto FileNodeIterator associato al vettore corners, che, come ogni altro iteratore C++, è necessario per scorrere nella sequenza di coppie e caricarle una ad una negli oggetti Point2f.
Di seguito un estratto del file YAML generato dal codice sopra descritto.
%YAML:1.0
cornerCount: 100
qualityLevel: 1.0000000000000000e-002
minDistance: 5.
blockSize: 20
corners:
- { x:158., y:458. }
- { x:88., y:275. }
- { x:82., y:278. }
- { x:274., y:276. }
- { x:111., y:255. }
- { x:111., y:246. }
Come sempre, per dubbi o per segnalare eventuali errori nell'articolo, vi invitiamo a lasciarci un commento.
