搭建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的资料相对较少;但是一旦配置成功、摸清楚其中的原理之后,就能够非常方便地将桌面图像程序算法移植过来,但是也需要注意算法移植过程中的一些小技巧。
            感谢阅读至此,希望有所帮助!