Windows下用Caffe跑自己的數據(遙感影像)


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;
        }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM