搭建Android+QT+OpenCV環境,實現「單色圖片著色」效果

  • 2019 年 10 月 3 日
  • 筆記

 
         OpenCV是我們大家非常熟悉的影像處理開源類庫;在其新版本將原本在Contrib分庫中的DNN模組融合到了主庫中,並且更新了相應文檔。這樣我們就能夠非常方便地利用OpenCV實現一些屬於DeepLearning範疇的效果,比如“超級解析度”“單色圖片著色”“色彩遷移”等。當我們想把軟體處理的平台由PC機轉移到嵌入式平台和手機上的時候,QT也是能和OpenCV配合地非常好的平台。在這裡,我具體研究了如何搭建Android+QT+OpenCV環境,實現“單色圖片著色”效果;並將相關內容整理如下,希望能夠對有這方面需求的工程師提供幫助。
一、環境配置
        首先我們面臨的問題是工具版本的選擇,雖然我們已經確定了Android+QT+OpenCV的基本軟體結構,但是在每一個環節都需要選擇具體的版本。
        Android需要選擇的是sdk和ndk的版本,我這裡使用的是Android10(API29)+android-ndk-r20的組合,基本上是現在(2019年9月)最新的組合了;
        QT需要選擇的是QT和QT Creator,我這裡選擇的是QT 5.13.1+QT Creator 4.10.0,同樣是現在(2019年9月)最新的組合;
        OpenCV用於Android的話,官方有Prebuild版本,我這裡採用的是opencv-4.1.0-android-sdk。
 
1、SDK的下載和配置
       首先我們正確安裝JDK並且將其目錄放入PATH中,而後需要下載android-sdk、android-ndk-r20、OpenCV-android-sdk,並且分別解壓放置在非中文無空格的目錄中。(下載地址看文末)
 
2、QT的安裝和配置
       下載QT在線安裝程式直接安裝,安裝過程中除了默認選中的“Developer and designer Tools”以外,只需選中QT5.13.1下的“Android Armv7”即可。
完成後主要是做一個配置“工具”->”選項“中”設備“欄目”JDK””SDK””NDK”都配置到正確的目錄下
這裡容易出現各種錯誤,儘可能保證使用我推薦的軟體版本,避免錯誤影響進度。
此時,你應該就已經可以在Android上運行QT自帶的例子。
         任意選擇一個例子,比如“Qt 3D: Audio Visualizer Example ”,選擇之前配置好的Kits,連接好實體手機或者模擬器(參考文末鏈接),點擊“運行”,稍等一會,即可出現效果。
        這裡需要注意,第一次運行可能需要從網路上下載一些東西,所以請保證網路順暢。
 
3、OpenCV環境的引入
 
       下面我們想辦法將OpenCV環境引入進來,之前已經下載了“OpenCV-android-sdk”,它的文件目錄是這樣:
這時,需要我們配置QT項目的.pro文件,最為重要的就是在.pro文件中添加這個模組
 
android {
ANDROID_OPENCV = D:/OpenCVandroidsdk/sdk/native
INCLUDEPATH +=
$$ANDROID_OPENCV/jni/include/opencv2
$$ANDROID_OPENCV/jni/include

LIBS +=
$$ANDROID_OPENCV/staticlibs/armeabiv7a/libopencv_ml.a
$$ANDROID_OPENCV/staticlibs/armeabiv7a/libopencv_objdetect.a
$$ANDROID_OPENCV/staticlibs/armeabiv7a/libopencv_calib3d.a
$$ANDROID_OPENCV/staticlibs/armeabiv7a/libopencv_video.a
$$ANDROID_OPENCV/staticlibs/armeabiv7a/libopencv_features2d.a
$$ANDROID_OPENCV/staticlibs/armeabiv7a/libopencv_highgui.a
$$ANDROID_OPENCV/staticlibs/armeabiv7a/libopencv_flann.a
$$ANDROID_OPENCV/staticlibs/armeabiv7a/libopencv_imgproc.a
$$ANDROID_OPENCV/staticlibs/armeabiv7a/libopencv_dnn.a
$$ANDROID_OPENCV/staticlibs/armeabiv7a/libopencv_core.a
$$ANDROID_OPENCV/3rdparty/libs/armeabiv7a/libcpufeatures.a
$$ANDROID_OPENCV/3rdparty/libs/armeabiv7a/libIlmImf.a
$$ANDROID_OPENCV/3rdparty/libs/armeabiv7a/liblibjasper.a
$$ANDROID_OPENCV/3rdparty/libs/armeabiv7a/liblibjpegturbo.a
$$ANDROID_OPENCV/3rdparty/libs/armeabiv7a/liblibpng.a
$$ANDROID_OPENCV/3rdparty/libs/armeabiv7a/liblibprotobuf.a
$$ANDROID_OPENCV/3rdparty/libs/armeabiv7a/liblibtiff.a
$$ANDROID_OPENCV/3rdparty/libs/armeabiv7a/liblibwebp.a
$$ANDROID_OPENCV/3rdparty/libs/armeabiv7a/libquirc.a
$$ANDROID_OPENCV/3rdparty/libs/armeabiv7a/libtbb.a
$$ANDROID_OPENCV/3rdparty/libs/armeabiv7a/libtegra_hal.a
$$ANDROID_OPENCV/libs/armeabiv7a/libopencv_java4.so

}

 
這裡就是告訴QT到哪裡去尋找OpenCV-android-sdk的include文件和libs文件,最後,還需要將libopencv_java4.so添加到項目中
 
 
 
方法是對於當前項目,點擊“項目”->“詳情”->”add”將這個libopencv_java4.so加進去,需要注意這裡有bug,添加完成後,需要手動修
 
改.pro文件這個部分至正確:
 
 
contains(ANDROID_TARGET_ARCH,armeabiv7a) {
ANDROID_EXTRA_LIBS =
D:/OpenCVandroidsdk/sdk/native/libs/armeabiv7a/libopencv_java4.so

 
4、DNN模型的引入
      
      由於所有的DNN模型都需要調用模型文件(.pb等),而這些文件都必須預先編譯到APK中去。使用 Qt 如何來做了?還是在.pro文
件上下功夫。
 
      打開 Qt 工程文件pro,並添加如下程式碼
 
data.files += images/*.*
data.files += dnn/*.prototxt
data.files += dnn/*.caffemodel
data.path = /assets/dnn
INSTALLS += data

 
 
注意,這裡只是我的示例寫法,images和dnn是我手動添加的和工程文件 pro 同級目錄的文件夾
裡面分別包含了圖片和模型文件:
 
再來看.pro中添加的這個部分
 
data.files += images/*.*
data.files += dnn/*.prototxt
data.files += dnn/*.caffemodel
data.path = /assets/dnn
INSTALLS += data

 
其中data欄位是可以隨便定義的,首先指定data.files 文件目錄,然後將images目錄下所有的文件,dnn目錄下所有的.prototxt和.caffemodel
 
全部加入其中。最後制定data.path為/assets/dnn
 
這樣編譯出來的 apk 中,加壓後會發現已經生成一個assets文件夾,並且在改文件夾中存放了我們已經添加的文件。
 
 
我們可以通過winrar打開apk,發現這些文件。

 
那麼到目前為止,所有需要準備的東西都已經停當,我們開始編碼。
 

二、程式碼編寫
這裡我給出的例子是一個非常簡單的widget程式(截圖可能和題圖有所不同,以這裡的為準)
 
包含1個textbox和2個button按鈕。我們直接按照從上到下的順序來看程式碼。
 
首先是”讀取Lena並顯示“,這個按鈕的功能比較存粹,就是使用OpenCV從前面保存的assets目錄中讀出lena.jpg,並且最終利用QT顯示出來。
 
void MainWindow::on_pushButton_2_clicked()
{
QFile::copy(“assets:/dnn/lena.bmp”, “lena.bmp”);
Mat src = imread(“lena.bmp”);
cvtColor(src,src,COLOR_BGR2GRAY);
QPixmap qpixmap = Mat2QImage(src);
// 將圖片顯示到label上
ui>label>setPixmap(qpixmap);
}

 
逐句來看,首先使用QFile::copy函數將“assets:/dnn/lena.bmp”拷貝到根目錄下的“lena.bmp”處,這樣OpenCV就能夠使用絕對路徑來讀取;
 
接下來讀入這個圖片,轉換為灰度圖片,這些都是基本OpenCV函數。然後我們使用了Mat2QImage函數將Mat格式的數據轉換成為了QPixmap格式,並且使用label顯示出來。
 
那麼Mat2QImage是一個我們自己實現的函數,功能就是將Mat格式轉換為QImage格式,這樣QT就能夠顯示。
 
//格式轉換
QPixmap Mat2QImage(Mat src)
{
QImage img;
//根據QT的顯示方法進行轉換
if(src.channels() == 3)
{
cvtColor( src, tmp, COLOR_BGR2RGB );
img = QImage( (const unsigned char*)(tmp.data), tmp.cols, tmp.rows, QImage::Format_RGB888 );
}
else
{
img = QImage( (const unsigned char*)(src.data), src.cols, src.rows, QImage::Format_Grayscale8 );
}
QPixmap qimg = QPixmap::fromImage(img) ;
return qimg;
}

其次是“調用著色演算法”,這個函數就是在前面的基礎上添加了較多功能。
 
void MainWindow::on_pushButton_clicked()
{
QFile::copy(“assets:/dnn/lena.jpg”, “lena.jpg”);
QFile::copy(“assets:/dnn/colorization_deploy_v2.prototxt”, “colorization_deploy_v2.prototxt”);
QFile::copy(“assets:/dnn/colorization_release_v2.caffemodel”, “colorization_release_v2.caffemodel”);
Mat src = imread(“lena.jpg”);
cvtColor(src,tmp,COLOR_BGR2GRAY);
cvtColor(tmp,src,COLOR_GRAY2BGR);

string modelTxt = “colorization_deploy_v2.prototxt”;
string modelBin = “colorization_release_v2.caffemodel”;
bool useOpenCL = true;

// fixed input size for the pretrained network
const int W_in = 224;
const int H_in = 224;
Net net = dnn::readNetFromCaffe(modelTxt, modelBin);
if (useOpenCL)
net.setPreferableTarget(DNN_TARGET_OPENCL);

// setup additional layers:
int sz[] = { 2, 313, 1, 1 };
const Mat pts_in_hull(4, sz, CV_32F, hull_pts);
Ptr<dnn::Layer> class8_ab = net.getLayer(“class8_ab”);
class8_ab>blobs.push_back(pts_in_hull);
Ptr<dnn::Layer> conv8_313_rh = net.getLayer(“conv8_313_rh”);
conv8_313_rh>blobs.push_back(Mat(1, 313, CV_32F, Scalar(2.606)));

// extract L channel and subtract mean
Mat lab, L, input;
src.convertTo(tmp, CV_32F, 1.0 / 255);
cvtColor(tmp, lab, COLOR_BGR2Lab);
extractChannel(lab, L, 0);
cv::resize(L, input, Size(W_in, H_in));
input -= 50;

// run the L channel through the network
Mat inputBlob = blobFromImage(input);
net.setInput(inputBlob);
Mat result = net.forward();

// retrieve the calculated a,b channels from the network output
Size siz(result.size[2], result.size[3]);
Mat a = Mat(siz, CV_32F, result.ptr(0, 0));
Mat b = Mat(siz, CV_32F, result.ptr(0, 1));
cv::resize(a, a, src.size());
cv::resize(b, b, src.size());

// merge, and convert back to BGR
Mat color, chn[] = { L, a, b };
merge(chn, 3, lab);
cvtColor(lab, color, COLOR_Lab2BGR);

color.convertTo(tmp,CV_8UC3,255);

QPixmap qpixmap = Mat2QImage(tmp);
// 將圖片顯示到label上
ui>label>setPixmap(qpixmap);
}

這個程式碼比較長,我們一塊一塊地來講,首先仍然是將assets目錄中的文件拷貝到絕對地址下面;
然後這個比較長的程式碼具體實現的功能就是調用模型實現著色效果,這裡我整編一些之前寫的東西。
 
目前使用的這個模型來自Richard Zhang,原始論文:
目前演算法能夠實現較好的灰度圖片作色效果,他所採用的方法是基於大量圖片的訓練來“預測”灰色圖片中對應的彩色效果。下圖是論文中的對比。
 
為了程式的成功運行,需要先前往

http://eecs.berkeley.edu/~rich.zhang/projects/2016_colorization/files/demo_v2/colorization_release_v2.caffemodel

https://raw.githubusercontent.com/richzhang/colorization/master/colorization/models/colorization_deploy_v2.prototxt

下載caffeemodel和prototxt文件。整個過程中很多程式碼,只有幾行是核心的:

 

其他的程式碼都是為了能夠將各種文件轉換成forward支援的格式。其中調用了一個大的常量:

static float hull_pts[] = {
90., 90., 90., 90., 90., 80., 80., 80., 80., 80., 80., 80., 80., 70., 70., 70., 70., 70., 70., 70., 70.,
70., 70., 60., 60., 60., 60., 60., 60., 60., 60., 60., 60., 60., 60., 50., 50., 50., 50., 50., 50., 50., 50.,
50., 50., 50., 50., 50., 50., 40., 40., 40., 40., 40., 40., 40., 40., 40., 40., 40., 40., 40., 40., 40., 30.,
30., 30., 30., 30., 30., 30., 30., 30., 30., 30., 30., 30., 30., 30., 30., 20., 20., 20., 20., 20., 20., 20.,
20., 20., 20., 20., 20., 20., 20., 20., 20., 10., 10., 10., 10., 10., 10., 10., 10., 10., 10., 10., 10., 10.,
10., 10., 10., 10., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 10., 10., 10., 10., 10., 10., 10.,
10., 10., 10., 10., 10., 10., 10., 10., 10., 10., 10., 20., 20., 20., 20., 20., 20., 20., 20., 20., 20., 20., 20., 20., 20., 20.,
20., 20., 20., 30., 30., 30., 30., 30., 30., 30., 30., 30., 30., 30., 30., 30., 30., 30., 30., 30., 30., 30., 40., 40., 40., 40.,
40., 40., 40., 40., 40., 40., 40., 40., 40., 40., 40., 40., 40., 40., 40., 40., 50., 50., 50., 50., 50., 50., 50., 50., 50., 50.,
50., 50., 50., 50., 50., 50., 50., 50., 50., 60., 60., 60., 60., 60., 60., 60., 60., 60., 60., 60., 60., 60., 60., 60., 60., 60.,
60., 60., 60., 70., 70., 70., 70., 70., 70., 70., 70., 70., 70., 70., 70., 70., 70., 70., 70., 70., 70., 70., 70., 80., 80., 80.,
80., 80., 80., 80., 80., 80., 80., 80., 80., 80., 80., 80., 80., 80., 80., 80., 90., 90., 90., 90., 90., 90., 90., 90., 90., 90.,
90., 90., 90., 90., 90., 90., 90., 90., 90., 100., 100., 100., 100., 100., 100., 100., 100., 100., 100., 50., 60., 70., 80., 90.,
20., 30., 40., 50., 60., 70., 80., 90., 0., 10., 20., 30., 40., 50., 60., 70., 80., 90., 20., 10., 0., 10., 20., 30., 40., 50.,
60., 70., 80., 90., 30., 20., 10., 0., 10., 20., 30., 40., 50., 60., 70., 80., 90., 100., 40., 30., 20., 10., 0., 10., 20.,
30., 40., 50., 60., 70., 80., 90., 100., 50., 40., 30., 20., 10., 0., 10., 20., 30., 40., 50., 60., 70., 80., 90., 100., 50.,
40., 30., 20., 10., 0., 10., 20., 30., 40., 50., 60., 70., 80., 90., 100., 60., 50., 40., 30., 20., 10., 0., 10., 20.,
30., 40., 50., 60., 70., 80., 90., 100., 70., 60., 50., 40., 30., 20., 10., 0., 10., 20., 30., 40., 50., 60., 70., 80., 90.,
100., 80., 70., 60., 50., 40., 30., 20., 10., 0., 10., 20., 30., 40., 50., 60., 70., 80., 90., 80., 70., 60., 50.,
40., 30., 20., 10., 0., 10., 20., 30., 40., 50., 60., 70., 80., 90., 90., 80., 70., 60., 50., 40., 30., 20., 10.,
0., 10., 20., 30., 40., 50., 60., 70., 80., 90., 100., 90., 80., 70., 60., 50., 40., 30., 20., 10., 0., 10., 20., 30.,
40., 50., 60., 70., 80., 90., 100., 90., 80., 70., 60., 50., 40., 30., 20., 10., 0., 10., 20., 30., 40., 50., 60., 70.,
80., 110., 100., 90., 80., 70., 60., 50., 40., 30., 20., 10., 0., 10., 20., 30., 40., 50., 60., 70., 80., 110., 100.,
90., 80., 70., 60., 50., 40., 30., 20., 10., 0., 10., 20., 30., 40., 50., 60., 70., 80., 110., 100., 90., 80., 70.,
60., 50., 40., 30., 20., 10., 0., 10., 20., 30., 40., 50., 60., 70., 110., 100., 90., 80., 70., 60., 50., 40., 30.,
20., 10., 0., 10., 20., 30., 40., 50., 60., 70., 90., 80., 70., 60., 50., 40., 30., 20., 10., 0.
};

這些數據的產生都和作者原始採用的模型有密切關係,想要完全理解Dnn的程式碼就必須了解對應模型的訓練過程。

 
參考和技巧
1、看到我這裡使用“夜神”模擬器來調試Android程式是不是很感興趣?具體使用起來是有技巧滴,請參考

如何使用”夜神“作為虛擬機來進行程式調試

 
2、在QTCreator的“工具->外部”選項下,可以配置一些外部程式,比如我把“夜神”和”SDK Manger”配置在這裡,方便使用
 
            
3、發現Qt Creatror使用designer修改了介面但是編譯無反應的解決方法,請具體參考
 
4、配置過程的參考建議。
        這裡分為3個步驟,首先是使用QT編寫Android程式,然後是實現Android+OpenCV,最後是Android+OpenCV+DNN,應該說是漸進方式的,每個步驟都有不同的參考資料。
 
        step1:配置QT編寫Android程式
 
 
         step3:Android+OpenCV+DNN

        
5、各個軟體下載地址,注意優先選擇X86_64版本
 
 
 
 
 
6、最後,提供完整的程式碼。但是你需要根據機器的實際情況進行修改
鏈接:https://pan.baidu.com/s/1oYo4iTihkKuG8eneBV7tmQ 
提取碼:00k9 
 

         總體感覺,開發基於Android的影像處理程式,是一件比較繁瑣的事情,可能出現問題的地方比較多,特別是QT的資料相對較少;但是一旦配置成功、摸清楚其中的原理之後,就能夠非常方便地將桌面影像程式演算法移植過來,但是也需要注意演算法移植過程中的一些小技巧。
            感謝閱讀至此,希望有所幫助!