1 前言
Caffe對於像我這樣的初學者來說是一款非常容易上手的深度學習框架。關於用Caffe跑自己的數據這樣的博客已經非常多,感謝前輩們為我們提供的這么好的學習資源。這里我主要結合我所在的行業,說下如何對跑通具有多通道多格式的遙感數據。
2 數據准備
Caffe封裝的非常好,要想將我們的數據運用於Caffe上,我們唯一要做的工作就是准備好Caffe支持的數據輸入格式(leveldb/lmdb)。
Caffe解決方案下有一個工程convert_imageset為我們提供了接口,主要是將圖像文件轉化為 Caffe支持的兩種數據格式。工程實現數據格式轉換主要經過以下幾個步驟:

在細讀這個工程文件是會發現,其數據讀取函數用的是OpenCV 的imread函數,在io.cpp。關於OpenCV的imread函數,這里不做詳細介紹,只說出其存在的問題:
1 對於圖像文件,imread不能讀取多波段數據(遙感圖像),超過4個波段的;
2 讀取的數據格式默認是CV_8UC(n),遙感數據明顯不符合要求。
因此,要想通過Caffe自帶的數據集轉換接口將多波段多數據類型的遙感圖像輸出為Caffe支持的leveldb和lmdb格式存在明顯的不合理問題。
關於遙感圖像的讀取,我想大家第一反應就是GDAL庫。因此,我嘗試在Caffe的解決方案下重寫數據轉換接口,利用GDAL庫來讀圖像,並將讀取的數據轉換為OpenCV的Mat數據格式,從而和圖1中流程的第二步接軌(GDAL數據讀取轉換為Mat格式可參看前面的博客)。但是后來一想,感覺這樣做有點多余,為啥不通過GDAL讀取的數據直接寫入到Caffe::Datum中呢。
后來仔細看了Caffe::Datum類,發現其存儲數據目前只支持uchar和float,如果讀者願意,我想還可以給Datum類添加其他的支持數據格式。但是,我覺得float格式已經滿足我的要求了。因此,我寫了一個簡單的函數,實現從GDAL讀取的數據到Datum的轉化,其代碼如下:
1 bool ReadImageToDatum(const std::string &imgfilename, 2 const int label, 3 Datum &datum) 4 { 5 GDALAllRegister(); 6 GDALDataset *poRemoteSensingImageDS = (GDALDataset*)GDALOpen(imgfilename.c_str(),GA_ReadOnly); 7 datum.set_channels(kRemoteSensingBandNums); 8 datum.set_height(kRemoteSensingSize); 9 datum.set_width(kRemoteSensingSize); 10 datum.set_label(label); 11 datum.clear_data(); 12 datum.clear_float_data(); 13 datum.set_encoded(false); 14 int *data = new int[kRemoteSensingImageNBytes]; 15 int *pBandMap = new int[kRemoteSensingBandNums]; 16 for (int b = 0; b < kRemoteSensingBandNums; b++){ 17 pBandMap[b] = b + 1; 18 } 19 GDALDataType ty = poRemoteSensingImageDS->GetRasterBand(1)->GetRasterDataType(); 20 poRemoteSensingImageDS->RasterIO(GF_Read, 0, 0, kRemoteSensingSize, kRemoteSensingSize, 21 data, kRemoteSensingSize, kRemoteSensingSize, ty, kRemoteSensingBandNums, 22 pBandMap, sizeof(int), kRemoteSensingSize*sizeof(int), 23 kRemoteSensingSize*kRemoteSensingSize*sizeof(int)); 24 for (int i = 0; i < kRemoteSensingImageNBytes; i++){ 25 datum.add_float_data((float)data[i]); 26 } 27 delete[]data; data = nullptr; 28 delete[]pBandMap; pBandMap = nullptr; 29 GDALClose((GDALDatasetH)poRemoteSensingImageDS); 30 return 1; 31 };
這里有個細節問題需要說下:因為我不大算動圖1第三步中的Caffe::Datum--》leveldb/lmdb這個過程,所以GDAL讀取的數據順序需要與Mat中圖像的存儲格式一樣。Mat默認數據存儲格式是:BIP,及按像元保存,即先保存第一個波段的第一個像元,之后保存第二波段的第一個像元,依次保存存儲。因此,在用GDAL讀取圖像的時候也應該用BIP格式讀取,確保一致。到此遙感數據集的轉換工作基本完成。我們可以將具有多波段和多數據類型的遙感數據順利的保存為leveldb或者lmdb。我想其他的數據類型也可參考類似的方法。可以自己制作一個統一的二進制文件格式,然后輕松實現轉換。
3 均值計算
這一步沒有需要改動的地方,compute_image_mean 工程提供的接口完全可以支持之前Datum中的uchar和float兩種數據格式。
1 if (data.size() != 0) { 2 CHECK_EQ(data.size(), size_in_datum); 3 for (int i = 0; i < size_in_datum; ++i) { 4 sum_blob.set_data(i, sum_blob.data(i) + (uint8_t)data[i]); 5 } 6 } else { 7 CHECK_EQ(datum.float_data_size(), size_in_datum); 8 for (int i = 0; i < size_in_datum; ++i) { 9 sum_blob.set_data(i, sum_blob.data(i) + 10 static_cast<float>(datum.float_data(i))); 11 } 12 }
4 模型訓練
這個過程也沒有需要改動的,設置好網絡參數,利用Caffe.exe提供的接口就可以順利的完成模型的訓練工作。
5 分類
分類同樣存在之前數據准備中出現的問題,因此,還是要重寫classification工程,主要在於圖像的讀取部分,並將用GDAL讀取的數據,轉化為Mat的多通道數據。具體不說了,上傳部分代碼供大家參考:
float *readPatchImage = new float[kRemoteSensingSize*kRemoteSensingSize*bandNums]; int leftX = colIndex - constWidth; if (leftX + kRemoteSensingSize > width) leftX = width - kRemoteSensingSize - 1; poRemoteSensingImageDS->RasterIO(GF_Read, leftX, leftY, kRemoteSensingSize, kRemoteSensingSize, readPatchImage, kRemoteSensingSize, kRemoteSensingSize, GDT_Float32, bandNums, pBandMap, bandNums*sizeof(float), bandNums*kRemoteSensingSize*sizeof(float),sizeof(float)); cv::Mat img = cv::Mat(kRemoteSensingSize, kRemoteSensingSize, CV_32FC(bandNums), readPatchImage); std::vector<Prediction> predictions = classifier.Classify(img); if (predictions[0].first == "0") { std::cout << "-----Find Suspicious Chinmey: Probability:" << predictions[0].second << std::endl; std::cout << " Position:leftX:" << leftX << " leftY:" << leftY << std::endl; std::cout << std::endl; unsigned char* buf = new unsigned char[kRemoteSensingSize*kRemoteSensingSize]; int i = 0; while (i < kRemoteSensingSize*kRemoteSensingSize) buf[i++] = 1; poOutBand->RasterIO(GF_Write, leftX, leftY, kRemoteSensingSize, kRemoteSensingSize, buf, kRemoteSensingSize, kRemoteSensingSize, GDT_Byte, 0, 0); delete[]buf; buf = nullptr; } delete[]readPatchImage; readPatchImage = nullptr; }
