OCR技术资料

技术博客

白翔:趣谈“捕文捉字”– 场景文字检测 | VALSE2017之十
https://mp.weixin.qq.com/s/Y7Xpe1DlhGR9XRB7GunGnA
人工智能前沿讲习班【AIDL专栏】白翔:基于合成数据的场景文本深度表示方法[附PPT]
https://mp.weixin.qq.com/s?src=11&timestamp=1522043323&ver=777&signature=*A0URm9g4ud4Xy7F*1ai37DFvvLyKVtYja139Z1KqXKm2Qd67FV
腾讯数平精准推荐 | OCR技术之检测篇
https://mp.weixin.qq.com/s?src=11&timestamp=1522054897&ver=777&signature=ukxuWvKzmk7gl9mnhecv*BYnjrhadrcvKZCdzgTENkWLzr-4u4iN6rxHoKzJ00oYKSI31EwnJMj0uZC2r7zeMQ*33uGPJKb6tVBdVBrmGFczm2EImZzRh3ba6Z0KsmHb&new=1
腾讯数平精准推荐 | OCR技术之识别篇
https://mp.weixin.qq.com/s?src=11&timestamp=1522054897&ver=777&signature=1n1eLHQ6UH-0ybeJSMnrqcA*5JHNHr5ueOx-Inburx*Lzbs9qFzlW4UOc4xD4Llywovu9np8Kj-0TVEhfwGyXkFhhGbzqaRdb6TxRQGflKNZrCQgQgJ76pTSZ0dSz0Gy&new=1
资源 | 百万级字符:清华大学提出中文自然文本数据集CTW
https://mp.weixin.qq.com/s?__biz=MzA3MzI4MjgzMw==&mid=2650738568&idx=3&sn=92a17c297fafa5e5da25e53ad153cb8e&chksm=871acbf6b06d42e07ea521c508216eb7ff975222ecfb31171d7d940e8023f3e1e4956ddde274#rd

手写笔记的图像压缩增强算法

介绍:https://mzucker.github.io/2016/09/20/noteshrink.html
代码:https://github.com/mzucker/noteshrink

这篇首先是找到背景颜色,具体是从2000x2000大小的图像中随机抽取5%的像素,然后找到像素值最多的像素值作为背景填充色,实际中占比最多的像素通常仍然占比较低,这里把8位图像深度降低到6位来提高最多像素的占比,降低误差。
背景的填充方法首先将颜色空间转换到HSV空间,并将该像素在S和V附近的值都用该背景颜色统一表示。
剩余的像素就是前景了,对其进行聚类,得到聚类中心,并将得到的聚类进行颜色增强,并将类别的像素都用聚类中心填充。

Chinese Text in the Wild

https://ctwdataset.github.io/
清华放出来的一个非常全面的中文ocr的实现,包含实现代码、数据集、训练好的模型。
数据集有32285张图像和1018402个中文字符。

开源实现

ctpn+crnn中文识别
https://github.com/bear63/sceneReco
ctpn+crnn英文识别
https://github.com/AKSHAYUBHAT/DeepVideoAnalytics/tree/master/docs/experiments/ocr
crnn
https://github.com/AimeeKing/crnn-tensorflow
https://github.com/Belval/CRNN/tree/master/CRNN
验证码识别
https://github.com/ypwhs/captcha_break

caffe2学习(三)推理预测C++

编译caffe2

看一下caffe2的CMakeLists.txt,其中很多模块都是服务端c++ inference时候用不到的

在默认配置的基础上,其他用不到的模块有

  • BUILD_PYTHON OFF
  • BUILD_TEST OFF
  • USE_CUDA OFF
  • USE_LEVELDB OFF
  • USE_LMDB OFF
  • USE_METAL OFF
  • USE_MOBILE_OPENGL OFF
  • USE_MPI OFF
  • USE_NCCL OFF
  • USE_NNPACK OFF
  • USE_OPENCV OFF
  • USE_OPENMP ON (很影响inference的性能)

inference工程

CMake配置

CMakeLists.txt需要注意的几个点

add_definitions(-DCAFFE2_USE_GOOGLE_GLOG)
add_definitions(-DCAFFE2_USE_GFLAGS)

set(CMAKE_SKIP_BUILD_RPATH FALSE)
set(CMAKE_INSTALL_RPATH $ORIGIN)
set(CMAKE_INSTALL_RPATH_USE_LINK_PATH FALSE)
set(CMAKE_BUILD_TYPE Release)

include

inference会用到的caffe2的几个头文件都在core\proto\utils几个文件夹中

#include <iostream>
#include <string>
#include <vector>
#include <memory>
#include <opencv2/opencv.hpp>
#include <caffe2/core/init.h>
#include <caffe2/core/predictor.h>
#include <caffe2/utils/proto_utils.h>

using namespace std;
using namespace cv;
using namespace caffe2;

class Caffe2Net {
public:
    Caffe2Net(string initNet,string predictNet);
    vector<float> predict(Mat img);
protected:
    TensorCPU preProcess(Mat img);
    vector<float> postProcess(TensorCPU output);

    Workspace workspace;
    unique_ptr<NetBase> predict_net;
};

net init

Caffe2Net::Caffe2Net(string initNet, string predictNet)
:workspace(nullptr)
{
#ifdef WITH_CUDA
    DeviceOption option;
    option.set_device_type(CUDA);
    new CUDAContext(option);
#endif
    NetDef init_net_def, predict_net_def;
    CAFFE_ENFORCE(ReadProtoFromFile(initNet, &init_net_def));
    CAFFE_ENFORCE(ReadProtoFromFile(predictNet, &predict_net_def));
#ifdef WITH_CUDA
    init_net_def.mutable_device_option()->set_device_type(CUDA);
    predict_net_def.mutable_device_option()->set_device_type(CUDA);
#else
    init_net_def.mutable_device_option()->set_device_type(CPU);
    predict_net_def.mutable_device_option()->set_device_type(CPU);    
#endif
    workspace.RunNetOnce(init_net_def);
    predict_net = CreateNet(predict_net_def,&workspace);
}

image preprocess

TensorCPU Caffe2Net::preProcess(Mat img) {
    // resize and crop
    cv::resize(image, image, cv::Size(256, 256));
    image = image(cv::Rect(16,16,224,224));
    // convert to float, normalize to [-1,1]
    image.convertTo(image, CV_32FC3, 1.0, -128);
    image = image*0.0078125
    // convert NHWC to NCHW
    vector<cv::Mat> channels(3);
    cv::split(image, channels);
    std::vector<float> data;
    for (auto &c : channels) {
        data.insert(data.end(), (float *)c.datastart, (float *)c.dataend);
    }
    std::vector<TIndex> dims({1, 3, 224, 224});
    return TensorCPU(dims, data, NULL);
}

net inference

vector<float> Caffe2Net::predict(Mat img)
{
    //create input blob
#ifdef WITH_CUDA
    TensorCUDA input = TensorCUDA(preProcess(img));
    auto tensor = workspace.CreateBlob("data")->GetMutable<TensorCUDA>();
#else
    TensorCPU input = preProcess(img);
    auto tensor = workspace.CreateBlob("data")->GetMutable<TensorCPU>();
#endif
    tensor->ResizeLike(input);
    tensor->ShareData(input);
    //predict
    predict_net->Run();
    //get output blob
#ifdef WITH_CUDA
    TensorCPU output = TensorCPU(workspace.GetBlob("fc1")->Get<TensorCUDA>());
#else
    TensorCPU output = TensorCPU(workspace.GetBlob("fc1")->Get<TensorCPU>());
#endif
    return postProcess(output);
}

vector<float> Caffe2Net::postProcess(TensorCPU output)
{
    const float * probs = output.data<float>();
    vector<TIndex> dims = output.dims();
    assert(2 == output.ndim());
    assert(1 == dims[0]);
    assert(512 == dims[1]);
    vector<float> retVal(dims[1]);
    copy(probs, probs+dims[1], retVal.begin());
    return retVal;
}

destruction

google::protobuf::ShutdownProtobufLibrary();

tips

修改spatialBN源码支持fc layer

原生的caffe2只支持卷积层的batch norm操作,有点尴尬,github上也有人遇到过这个问题https://github.com/caffe2/caffe2/issues/865,给出的方案也很姜,将fc层reshape到4通道,最后两通道设为1,再做spatial_bn,但这种实现不太优美,看了caffe2的spatial_bn的实现后,通道的限制其实没有必要,所以修改源码

vim caffe2/operators/spatial_batch_norm_op.cc
//CAFFE_ENFORCE(X.ndim() >= 3 && X.ndim() <= 5);
CAFFE_ENFORCE(X.ndim() >= 2 && X.ndim() <= 5);
const int N = X.dim32(0);
const int C =
    (order_ == StorageOrder::NCHW ? X.dim32(1) : X.dim32(X.ndim() - 1));
//const int H = (order_ == StorageOrder::NCHW ? X.dim32(2) : X.dim32(1));
const int H = X.ndim() > 2
    ? (order_ == StorageOrder::NCHW ? X.dim32(2) : X.dim32(1))
    : 1;

商品识别拍照购资料

技术blog

拍立淘-以图搜图中的图像搜索算法
https://yq.aliyun.com/articles/3225?spm=5176.100238.yqhn2.8.l57Lh3&comefrom=http://blogread.cn/news/
阿里研究员华先胜:图像搜索的前世今生
http://www.36dsj.com/archives/50681
基于深度学习的商品检索技术
https://zhuanlan.zhihu.com/p/22451308
首次披露!阿里拍立淘技术框架及核心算法,日均UV超千万
https://mp.weixin.qq.com/s/KBYqIGebZDlPo-xptyJCNw
图像算法在电商大促中的应用浅析
https://mp.weixin.qq.com/s/hbvM64QPT1mKQZey6Kauww

linux-tips

linux内存free理解

  • total:是总的物理内存
  • used:使用中的内存
  • free:完全空闲的内存
  • shared:多个进程共享的内存 
  • buffers:写缓存,在写入磁盘之前,先把数据缓存一段时间,可以释放
  • cache:读缓存,读取过的文件,会缓存一段时间。可以释放
  • -buffers/cache:应用程序实际使用中的内存大小,等于used-buffers-cached
  • +buffers/cache:可供使用的内存总量,等于free+buffers+cached
  • Swap:不解释,都能看懂的
    所以,可以供程序使用的内存,应该看+buffers/cache,而不是free,这是新手最容易犯的错误之一。那位可能要问了,明明已经开始使用swap了,怎么能说内存还充足呢?照我的理解是,系统在分配内存的时候,如果发现内存不足,会释放一批旧的cache,把空间腾出来给新申请的进程用,有时候释放不充分或者不及时,于是开始使用到swap了!

系统启动自运行脚本

使用rc.local

sudo chmod +x /etc/rc.d/rc.local
sudo vim /etc/rc.d/rc.local
sh /home/admin/script.sh &

使用Crontab

crontab -e
@reboot (sleep 90; sh \home\admin\script.sh)

ReID资源汇总

论文

Person Re-identification: Past, Present and Future
https://arxiv.org/abs/1610.02984
Gated Siamese Convolutional Neural Network Architecture for Human Re-Identification
https://arxiv.org/abs/1607.08378
Deep Hybrid Similarity Learning for Person Re-identification
https://arxiv.org/abs/1702.04858
A Discriminatively Learned CNN Embedding for Person Re-identification
https://arxiv.org/abs/1611.05666
code:https://github.com/layumi/2016_person_re-ID

blog

行人对齐+重识别网络:Pedestrian Alignment Network for Large-scale Person Re-identification
https://zhuanlan.zhihu.com/p/29269953

人脸算法资源汇总

人脸检测

MTCNN

作者原版caffe+matlabhttps://github.com/kpzhang93/MTCNN_face_detection_alignment
caffe+c++https://github.com/foreverYoungGitHub/MTCNN
mtcnn-lighthttps://github.com/dlunion/mtcnn
MTCNN-caffehttps://github.com/blankWorld/MTCNN-Accelerate-Onet

How far are we from solving the 2D & 3D Face Alignment problem?

https://github.com/1adrianb/2D-and-3D-face-alignment

dlib

dlib-androidhttps://github.com/tzutalin/dlib-android
dlib-android-experimenthttps://github.com/boyw165/my-dlib-experiment/

RSA

https://github.com/sciencefans/RSA-for-object-detection

人脸关键点

https://github.com/1adrianb/2D-and-3D-face-alignment

人脸识别

FaceNet

https://github.com/davidsandberg/facenet
https://github.com/hizhangp/triplet

Face Recognition Use A-Softmax_loss on light cleaned Ms_celeb-1M dataset

https://github.com/KaleidoZhouYN/Details-on-Face-Recognition

SphereFace

https://github.com/wy1iu/sphereface

COCO Loss

https://github.com/sciencefans/coco_loss

NormFace

https://github.com/happynear/NormFace

A Light CNN for Deep Face Representation with Noisy Labels

https://github.com/AlfredXiangWu/face_verification_experiment

人脸活体检测

https://github.com/number9473/nn-algorithm/issues/36

人脸朝向

https://www.learnopencv.com/head-pose-estimation-using-opencv-and-dlib/
https://www.learnopencv.com/rotation-matrix-to-euler-angles/
https://github.com/chili-epfl/attention-tracker

人脸清晰度检测

Blur Detection for Digital Images Using Wavelet Transform
https://github.com/huneng/blur-detect-use-haar-wavelet

微表情识别

Micro-expression-Detection-System
思想:检测人眼、人鼻、人嘴位置,然后对检测的区域做光流法判断表情变化,如果有变化,判断有微表情。
https://github.com/kamilste/Micro-expression-Detection-System

人脸属性

https://github.com/tornadomeet/mxnet-face

人脸颜值

SCUT-FBP5500-Database
https://github.com/HCIILAB/SCUT-FBP5500-Database-Release

网纹去除

数字图像处理–掩膜重建
http://blog.csdn.net/i_chaoren/article/details/54411569
人脸图像保护和网纹人脸识别–李志航
https://mp.weixin.qq.com/s?src=11&timestamp=1513759696&ver=585&signature=0Tbw-Jx9ffdWd86x1QChx7Pdbs3IUOBwn6xg63bjdPFsbK79M9JnX4aPOUGvVo3FISXSpgp7cSU3o4pdLEwREbrcbU0NGe1WqqPFqXWggBPPceyT4fiPQJUfey6geFxH&new=1

人脸资讯数据资源汇总

资讯

访谈百度IDL林元庆:百度大脑如何在人脸识别上战胜人类「最强大脑」
https://www.jiqizhixin.com/articles/2017-01-09
新智元–首发:人脸识别世界杯榜单出炉,微软百万名人识别竞赛冠军分享
https://mp.weixin.qq.com/s?__biz=MzI3MTA0MTk1MA==&mid=2652001114&idx=1&sn=decf0edb4f21dba0925c002f7c0ef0e2
新智元–【世界最大人脸对齐数据集】ICCV 2017:距离解决人脸对齐已不远
https://mp.weixin.qq.com/s/s5HL6y2P9_KqpSAQg08URw
雷锋网–详解苹果Face ID,将让深度摄像头成主流
https://mp.weixin.qq.com/s?__biz=MTM2ODM0ODYyMQ==&mid=2651429348&idx=1&sn=89b82730d645e2de518cf3706d6e6e40
深度学习大讲堂–韩琥:深度学习让机器给人脸“贴标签”
https://mp.weixin.qq.com/s/CLgyaAslE4hYHi62UhzeGw
SeetaFace开源人脸识别引擎介绍
http://blog.csdn.net/u013146742/article/details/52816640
深度学习大讲堂–【Technical Review】ECCV16 Center Loss及其在人脸识别中的应用
https://zhuanlan.zhihu.com/p/23340343
雷锋网–CNCC 2016 | 山世光:深度化的人脸检测与识别技术—进展与展望
https://www.leiphone.com/news/201610/rZ2Mn9UFF3x8FaEt.html
人脸识别中的活体检测
https://zhuanlan.zhihu.com/p/25401788
深度学习大讲堂–人脸检测与识别年度进展概述
https://mp.weixin.qq.com/s?__biz=MzI1NTE4NTUwOQ==&mid=2650326590&idx=1&sn=c195e063bc4bcd60edb6b7a83a751067
海康威视–人脸识别的两大系统方案PK,结果是
https://mp.weixin.qq.com/s/qG8R4RMKXJizPmZyEUqyMA
海康威视–强势解码后端人脸智能分析应用,奥秘原来在这儿
https://mp.weixin.qq.com/s/4zOYZU20hREtiiLbfSrW2A
海康威视–人脸识别99%准确率背后的秘密
http://www.7its.com/html/2017/anli_1207/6115.html
《人工智能强势来袭,听旷视(Face++)为你揭开智能零售的秘密》干货分享
http://www.sohu.com/a/73894297_355045
人脸图像保护和网纹人脸识别–李志航
https://mp.weixin.qq.com/s?src=11&timestamp=1513759696&ver=585&signature=0Tbw-Jx9ffdWd86x1QChx7Pdbs3IUOBwn6xg63bjdPFsbK79M9JnX4aPOUGvVo3FISXSpgp7cSU3o4pdLEwREbrcbU0NGe1WqqPFqXWggBPPceyT4fiPQJUfey6geFxH&new=1
机器之心–从传统方法到深度学习,人脸关键点检测方法综述
https://mp.weixin.qq.com/s?__biz=MzA3MzI4MjgzMw==&mid=2650734809&idx=1&sn=f5dd841fb56b668dbc4dc4c2a668c195&chksm=871ac4a7b06d4db1befb03fb1616cea7ea158561125df55798209a2926bcfa31e131eb46d7d4#rd
Demystifying Face Recognition(1\2\3\4)
http://blcv.pl/static/2017/11/07/demystifying-face-recognition-i-introduction/
【VALSE 前沿技术选介17-06期】探究最陌生的老朋友Softmax
http://mp.weixin.qq.com/s/D4X_GMAwUu_WtC0layWZGA
新智元–【难度越大,优势越大】腾讯AI Lab刷新人脸识别与人脸检测国际记录
https://mp.weixin.qq.com/s/FcbCA7dHEeTpbCruYd4TjA
新智元–【深度】申省梅颜水成团队获国际非受限人脸识别竞赛IJB-A冠军,主要负责人熊霖技术分享
https://mp.weixin.qq.com/s/s9H_OXX-CCakrTAQUFDm8g
新智元–【微信身份证后的刷脸时代】活体识别告诉你为什么照片无法破解人脸系统
https://mp.weixin.qq.com/s/A1pbiU5PA9Owe69lGX9afw
机器之心–AAAI 2018 | 如何高效进行大规模分类?港中文联合商汤提出新方法
https://mp.weixin.qq.com/s/kLXJsHbBnRIFC3NLChPhzA
知乎–InsightFace - 使用篇, 如何一键刷分LFW 99.80%, MegaFace 98%.
https://zhuanlan.zhihu.com/p/33750684
知乎–深度挖坑:从数据角度看人脸识别中Feature Normalization,Weight Normalization以及Triplet的作用
https://zhuanlan.zhihu.com/p/33288325
深入浅出谈人脸识别技术
http://www.infoq.com/cn/articles/deep-learning-face-recognition
Head Pose Estimation using OpenCV and Dlib
https://www.learnopencv.com/head-pose-estimation-using-opencv-and-dlib/
https://www.learnopencv.com/rotation-matrix-to-euler-angles/
Blur detection with OpenCV
https://www.pyimagesearch.com/2015/09/07/blur-detection-with-opencv/
依图 1765 万元中标德清县公安局“雪亮工程”建设之人脸识别、车脸识别应用软件采购项目
http://www.sohu.com/a/225482558_465914

性能测试指标

http://blog.csdn.net/blueblood7/article/details/41823593

  • 注册失败率 failure-to-enrol rate FTE
    注册失败的用户在总注册用户中所占的比例
  • 错误采集率 failure-to-acquire rate FTA
    在辨识或验证的尝试中,采集不到样本或样本质量无法达到要求的比例
  • 错误不匹配率 false non-match rate FNMR
    正确的尝试样本被错误的判定微不匹配的比例
  • 错误匹配率 false match rate FMR
    零效攻击尝试样本被错误的判为匹配的比例
  • 错误拒绝率 false refect rate FRR
    验证识别过程中,真实者被错误判为拒绝的比例
  • 错误接受率 false accept rate FAR
    验证识别过程中,冒充者被错误判定为接受的比例
  • 检测错误权衡曲线 detection error trade-off curve
    DET曲线(x轴为FAR,y轴为FRR)
  • 接受者操作特性曲线 receiver operating character istic curve
    ROC曲线(x轴为FAR,y轴为TPR)

FAR = FMR (1 – FTA)
FRR = FTA + FNMR
(1 – FTA)

数据

MS-Celeb-1M: Recognizing One Million Celebrities in the Real World

http://www.msceleb.org/
微软人脸识别挑战比赛,相当于人脸识别领域的imagenet,包含10万人的约1000万人脸数据,是目前为止最大规模的数据集和比赛。

VGGFace2

http://www.robots.ox.ac.uk/~vgg/data/vgg_face2/
9131个id的331万人脸图像。
百度网盘: https://pan.baidu.com/s/1i4NYnuH 密码: dukf

CelebA: Large-scale CelebFaces Attributes (CelebA) Dataset

http://mmlab.ie.cuhk.edu.hk/projects/CelebA.html
香港中文大学组2015年搞的一个最新的目前最大的人脸集,包含10177个人,202599张人脸图片,而且每张图片有5个关键点标注信息以及40个2值属性,属性包括是否带眼睛,是否在笑,是否带帽子,是不是卷发,是否年轻,性别等等,是非常珍贵的人脸数据。

WIDER FACE: A Face Detection Benchmark

http://mmlab.ie.cuhk.edu.hk/projects/WIDERFace/
香港中文大学再放大招,2015年11月又推出人脸检测标注数据库,包含32203张图片,393703张人脸。其中50%的测试数据集并没有公开标注信息。

IMDB-WIKI

https://data.vision.ee.ethz.ch/cvl/rrothe/imdb-wiki/
有人脸位置、性别、年龄的标注信息,共52万的标注图片

CASIA WebFace Database

http://www.cbsr.ia.ac.cn/english/CASIA-WebFace-Database.html
10,575 subjects and 494,414 images
CASIA clean-list:
https://github.com/happynear/FaceVerification
http://zhengyingbin.cc/ActiveAnnotationLearning/

Labeled Faces in the Wild

http://vis-www.cs.umass.edu/lfw/
13,000 images and 5749 subjects

MSRA-CFW

http://research.microsoft.com/en-us/projects/msra-cfw/
202,792 images and 1,583 subjects.

MegaFace Dataset

http://megaface.cs.washington.edu
passwd:bRx!XOx%of
1 Million Faces for Recognition at Scale 690,572 unique people

FaceScrub

http://vintage.winklerbros.net/facescrub.html
A Dataset With Over 100,000 Face Images of 530 People.

FDDB

http://vis-www.cs.umass.edu/fddb
Face Detection and Data Set Benchmark. 5k images.

AFLW

https://lrs.icg.tugraz.at/research/aflw/
Annotated Facial Landmarks in the Wild: A Large-scale, Real-world Database for Facial Landmark Localization. 25k images.

AFW

http://www.ics.uci.edu/~xzhu/face/
Annotated Faces in the Wild. ~1k images.

3D Mask Attack Dataset]

https://www.idiap.ch/dataset/3dmad
76500 frames of 17 persons using Kinect RGBD with eye positions (Sebastien Marcel)

Audio-visual database for face and speaker recognition

https://www.idiap.ch/dataset/mobio
Mobile Biometry MOBIO http://www.mobioproject.org/

BANCA face and voice database

http://www.ee.surrey.ac.uk/CVSSP/banca/
Univ of Surrey

Binghampton Univ 3D static and dynamic facial expression database

http://www.cs.binghamton.edu/~lijun/Research/3DFE/3DFE_Analysis.html
(Lijun Yin, Peter Gerhardstein and teammates)

The BioID Face Database

https://www.bioid.com/About/BioID-Face-Database
BioID group

Biwi 3D Audiovisual Corpus of Affective Communication

http://www.vision.ee.ethz.ch/datasets/b3dac2.en.html
1000 high quality, dynamic 3D scans of faces, recorded while pronouncing a set of English sentences.

Cohn-Kanade AU-Coded Expression Database

http://www.pitt.edu/~emotion/ck-spread.htm
500+ expression sequences of 100+ subjects, coded by activated Action Units (Affect Analysis Group, Univ. of Pittsburgh.

CMU/MIT Frontal Faces

http://cbcl.mit.edu/software-datasets/FaceData2.html
Training set: 2,429 faces, 4,548 non-faces; Test set: 472 faces, 23,573 non-faces.

kaggle表情数据

https://www.kaggle.com/c/challenges-in-representation-learning-facial-expression-recognition-challenge/data
人脸表情数据集,7种表情(0=Angry, 1=Disgust, 2=Fear, 3=Happy, 4=Sad, 5=Surprise, 6=Neutral),训练集28709张图片,测试集3589张,像素48*48

人脸素描数据集

http://mmlab.ie.cuhk.edu.hk/archive/facesketch.html
606张人脸的素描和证件照的一一对应图像

caffe2学习(二)基础

写在前面

最近开始尝试了caffe2,主要是因为tensorflow的发展越来越奇怪,一种网络实现在tensorflow中有多种对应的实现方式,臃肿的函数表达,每月一次大更新没有向下兼容,越来越高昂的学习成本,加之最近其他几大深度学习框架对tensorflow有合围之势…太多的槽点了,让我有点想放弃tensorflow这个坑了。还是怀念caffe的简洁,于是还是捡起贾扬清大神的续集填坑吧。废话不多说了,还是看看caffe2的亮点吧

caffe2亮点

  • 支持分布式训练
  • 对移动端的deployment的支持
  • 基础计算单元改为Operators,相对于caffe的layers单元有很多改进

caffe模型导入caffe2

python -m caffe2.python.caffe_translator deploy.prototxt pretrained.caffemodel

torch模型导入caffe2

使用torch2caffe的工具,将caffe作为桥梁,不展开讨论了

caffe2学习(一)安装配置

相关依赖

caffe2的依赖比caffe要干净很多,必要的库比较少

  • cmake
  • git
  • glog
  • protobuf
  • CUDA (optional)
  • cudnn (optional)

caffe2安装

git clone --recursive https://github.com/caffe2/caffe2.git
cd caffe2
make -j8
cd build
sudo make install

# test
python -c 'from caffe2.python import core' 2>/dev/null && echo "Success" || echo "Failure"
python -m caffe2.python.operator_test.relu_op_test

# Environment Variables
export PYTHONPATH=$PYTHONPATH:/home/15072585/caffe2/build  #(add to ~/.bashrc)

tips

tar jxf gcc-4.9.4.tar.bz2
cd gcc-4.9.4
./configure --enable-checking=release --enable-languages=c,c++ --disable-multilib --prefix=/home/yang/gcc
make -j8
make install
export PATH=/home/yang/gcc/bin:$PATH
export LD_LIBRARY_PATH=/home/yang/gcc/lib64:$LD_LIBRARY_PATH
  • from caffe2.python import core报错No module named past.builtins,解决方案pip install future

caffe学习(九)inference代码优化

多线程inference问题

对于caffe前向算法的使用,通常会遇到一种情况:一次初始化,多次进行predict方法,按照caffe的设计,shared_ptr是线程不安全的,如果同时起多个线程同时进行predict,会导致数据参数共享,进而出错。为此需要修改caffe教程中的实现,在每次predict的时候在内存中复制一份网络。

// old init
shared_ptr<Net<float> > net_;
net_.reset(new Net<float>(deploy_file, TEST));
net_->CopyTrainedLayersFrom(trained_file);

// new init
shared_ptr<Net<float> > net_;
NetParameter param_;
ReadNetParamsFromTextFileOrDie(deploy_file, &param_);
param_.mutable_state()->set_phase(TEST);
net_.reset(new Net<float>(param_));
net_->CopyTrainedLayersFrom(trained_file);

// old predict
net_->Forward();

// new predict
shared_ptr<Net<float> > net_tmp;
net_tmp.reset(new Net<float>(param_));
net_tmp->ShareTrainedLayersWith(net_.get());
net_->Forward();

网络复制优化

上面这段代码基本可以解决问题,由于ShareTrainedLayersWith是从内存中将网络参数复制过来,效率很高。但new Net<float>(param_)这个方法由于要基于param_重新构建一个网络并初始化参数,这个性能是很低的,而且对于predict来说也是没有意义的,因为后面还会用ShareTrainedLayersWith方法再次重新赋值。通常weight_filler还使用xavier或者gauss初始化,更加重了初始化的计算量,即使忽略这个参数,也还是会用constant进行初始化。为此,我们只能更改caffe的源码来屏蔽weight和bias初始化的动作。

vim src/caffe/layers/inner_product_layer.cpp
##################
//weight_filler->Fill(this->blobs_[0].get());
//bias_filler->Fill(this->blobs_[1].get());
##################
vim src/caffe/layers/base_conv_layer.cpp
##################
//weight_filler->Fill(this->blobs_[0].get());
//bias_filler->Fill(this->blobs_[1].get());

而且predict的过程,由于没有backward方法,也不需要使用net中的diff参数。所以在初始化的过程中也可以屏蔽。

vim src/caffe/blob.cpp
##################
//diff_.reset(new SyncedMemory(capacity_ * sizeof(Dtype)));

vtune使用

上述这种优化都是通过vtune工具分析代码的运行时间分析了程序初始化过程中的耗时的点,从而进行的优化。vtune大法好,vtune是intel的代码性能优化工具。

vtune_amplifier_xe/bin64/amplxe-gui

试题整理

mAP指标的含义

平均准确率均值mAP(Mean Average Precision)
对于每类事件根据每个样本得到的预测分值绘制准确率-召回率(precision-recall)曲线
计算曲线下区域面积即AP(Average precision)
对所有事件类别计算AP的均值,即mAP

caffe实现中使用了大量的虚函数、类模板,请介绍一下虚函数、类模板以及虚基类的使用场景

虚函数:是使基类指针指向派生类,用来方便通过基类调用不同派生类的函数实现,例如caffe中各种layer的forward方法。
类模板:多个类有共同操作,只是数据类型不同时候使用类模板。
虚基类:主要用在多重继承的派生类中,使派生类只保留间接基类的一份成员,多重继承较为复杂容易出错,不推荐这种用法

softmaxloss在深度学习中为何最为流行,有哪些优势;为何将softmax和交叉熵损失放在一起,有什么好处

https://zhuanlan.zhihu.com/p/25723112
http://freemind.pluskid.org/machine-learning/softmax-vs-softmax-loss-numerical-stability/
这两篇文章对于softmax解释的非常好,在深度学习中使用最为广泛,有两个原因,一是其输出为类别的概率值,二是因为其梯度求导特别简单。交叉熵的函数形式为

Loss = -sum(y_i*ln(z_i)) // z_i为softmax输出,y_i为ground_truth

由于softmax的特点,其导数特别友好,非常方便计算。如果将softmax和交叉熵分成两部分,就会多计算一个乘z_j再除z_j的过程,由于z_j为softmax输出,很可能非常小,会有可能导致float数据类型溢出,从而结果错误。

tensorflow学习(十二)加载自定义图像数据集

之前介绍了一种官方的TFRecords序列化方法,感觉过于复杂,这次实践一次自定义数据集的数据加载方案。介绍几个关键的步骤。

数据读取

  • 获取数据集的图像文件以及对应标签文件的文件名列表
  • 讲文件名列表转换为tensor格式
  • 根据文件名列表读取图像并转换为tensor,并进行预处理
  • 产生一个batch的数据

其中前3个方法在ImageReader初始化过程中实现,最后一个步骤在dequeue方法中实现,实例如下:

def read_labeled_image_list(data_dir, data_list):
    f = open(data_list, 'r')
    images = []
    labels = []
    for line in f:
        image, label = line.strip("\n").split(' ')
        images.append(data_dir + image)
        labels.append(int(label))
    return images, labels

def read_images_from_disk(input_queue, input_size, random_scale): 
    img_contents = tf.read_file(input_queue[0])
    img = tf.image.decode_jpeg(img_contents, channels=3)
    if input_size is not None:
        h = input_size
        w = input_size
        if random_scale:
            scale = tf.random_uniform([1], minval=0.75, maxval=1.25, dtype=tf.float32, seed=None)
            h_new = tf.to_int32(tf.mul(tf.to_float(tf.shape(img)[0]), scale))
            w_new = tf.to_int32(tf.mul(tf.to_float(tf.shape(img)[1]), scale))
            new_shape = tf.squeeze(tf.pack([h_new, w_new]), squeeze_dims=[1])
            img = tf.image.resize_images(img, new_shape)
        img = tf.image.resize_image_with_crop_or_pad(img, h, w)
    # RGB -> BGR.
    img_r, img_g, img_b = tf.split(split_dim=2, num_split=3, value=img)
    img = tf.cast(tf.concat(2, [img_b, img_g, img_r]), dtype=tf.float32)
    # Extract mean.
    img -= np.array((104.008,116.669,122.675), dtype=np.float32)

    label = tf.cast(input_queue[1], tf.int32)
    return img, label

class ImageReader(object):
    def __init__(self, data_dir, data_list, input_size, random_scale):
        self.data_dir = data_dir
        self.data_list = data_list
        self.input_size = input_size

        self.image_list, self.label_list = read_labeled_image_list(self.data_dir, self.data_list)
        self.images = tf.convert_to_tensor(self.image_list, dtype=tf.string)
        self.labels = tf.convert_to_tensor(self.label_list, dtype=tf.string)
        self.queue = tf.train.slice_input_producer([self.images, self.labels],
                                                   shuffle=input_size is not None) # Not shuffling if it is val.
        self.image, self.label = read_images_from_disk(self.queue, self.input_size, random_scale) 

    def dequeue(self, num_elements):
        image_batch, label_batch = tf.train.batch([self.image, self.label], num_elements)
        return image_batch, label_batch

线程和队列

Coordinator类负责多个线程的同步工作和同步终止,tf.train.start_queue_runners负责创建一组线程。推荐的使用模版

init_op = tf.global_variables_initializer()
sess = tf.Session()
sess.run(init_op)

# Start input enqueue threads.
coord = tf.train.Coordinator()
threads = tf.train.start_queue_runners(sess=sess, coord=coord)

try:
    while not coord.should_stop():
        sess.run(train_op)
except tf.errors.OutOfRangeError:
    print 'Done training -- epoch limit reached'
finally:
    coord.request_stop()

coord.join(threads)
sess.close()

调用实例

# image_batch, label_batch = tf.train.batch([image, label], 32)
reader = ImageReader()
image_batch, label_batch = reader.dequeue(32)
loss = loss = tf.nn.softmax_cross_entropy_with_logits(prediction, label_batch)

init_op = tf.global_variables_initializer()
sess = tf.Session()
sess.run(init_op)

coord = tf.train.Coordinator()
threads = tf.train.start_queue_runners(sess=sess, coord=coord)

try:
    for step in range(args.num_steps):
        loss_value = sess.run(loss)
except tf.errors.OutOfRangeError:
    print 'Done training -- epoch limit reached'
finally:
    coord.request_stop()

coord.join(threads)
sess.close()

tensorflow学习(十一)保存和恢复模型

保存/恢复对象

tf.train.Saver这个类负责保存和恢复模型。实际使用中,可以在模型训练的不同阶段保存多个checkpoints。为了防止硬盘被写满,保存模型过大,实际中保存n个最近的checkpoints。

# save
v1 = tf.Variable(1.0, name="v1")
saver = tf.train.Saver()  # global
saver_local = tf.train.Saver({'v1': v1})  # local
sess = tf.Session()
for step in xrange(1000000):
    sess.run(..training_op..)
    if step % 1000 == 0:
        saver.save(sess, 'my-model', global_step=step)  ==> filename: 'my-model-1000'

# restore
saver.restore(sess, 'my-model-1000')
saver.restore(sess, saver.latest_checkpoint())  # 可以自动传入参数路径

执行save方法之后,会生成一系列的文件,最好是单独放在一个路径中。

利用模型fine-tune

vgg_saver = tf.train.import_meta_graph('vgg16.meta')
vgg_graph = tf.get_default_graph()

self.x_plh = vgg_grah.get_tensor_by_name('input:0')

# 选择想要保留的层
output_conv =vgg_graph.get_tensor_by_name('conv1_2:0')
# output_conv =vgg_graph.get_tensor_by_name('conv2_2:0')
# output_conv =vgg_graph.get_tensor_by_name('conv3_3:0')

output_conv_sg = tf.stop_gradient(output_conv)

# 添加自己的网络结构
output_conv_shape = output_conv_sg.get_shape().as_list()
W1 = tf.get_variable('W1', shape=[1, 1, output_conv_shape[3], 32], initializer=tf.random_normal_initializer(stddev=1e-1))
b1 = tf.get_variable('b1', shape=[32], initializer=tf.constant_initializer(0.1))
z1 = tf.nn.conv2d(output_conv_sg, W1, strides=[1, 1, 1, 1], padding='SAME') + b1
a = tf.nn.relu(z1)

从caffe模型加载参数

tensorflow其实可以从任意算法,我们这里用caffe的模型参数为例

from six.moves import cPickle

net = caffe.Net(args.prototxt, args.caffemodel, caffe.TEST)

net_skeleton = list() 
for name, item in net.params.iteritems():
    net_skeleton.append([name + '/w', item[0].data.shape[::-1]])
    net_skeleton.append([name + '/b', item[1].data.shape])
with open(os.path.join(args.output_dir, 'net_skeleton.ckpt'), 'wb') as f:
    cPickle.dump(net_skeleton, f, protocol=cPickle.HIGHEST_PROTOCOL)

net_weights = dict()
for name, item in net.params.iteritems():
    net_weights[name + '/w'] = item[0].data.transpose(2, 3, 1, 0)
    net_weights[name + '/b'] = item[1].data
with open(os.path.join(args.output_dir,'net_weights.ckpt'), 'wb') as f:
    cPickle.dump(net_weights, f, protocol=cPickle.HIGHEST_PROTOCOL)
del net, net_skeleton, net_weights

保存好上述模型后,通过下面的方法加载即可

saver = tf.train.Saver(var_list=trainable)
saver.restore(sess, os.path.join(args.output_dir,'net_weights.ckpt'))

tensorflow学习(十)实用工具介绍

compatibility升级代码到兼容1.0版本

更新脚本

# file
tf_upgrade.py --infile <input_code> --outfile <output_code>
# dir
tf_upgrade.py --intree <input_dir> -outtree <output_dir>

执行过程中的新旧函数改动详见report.txt

tips

  • 注意升级代码之前不要手工更改代码

  • 升级后的部分函数无法更新,需要根据日志再手工更新部分函数

tensorflow学习(九)1.0正式版安装配置

近期tensorflow发布了正式版,我们也赶快更新起来。

安装环境

操作系统centOS 7.0, k80四卡。注意tensorflow1.0.0只支持CUDA8,所以需要将CUDA升级到8.0,cudnn升级到v5.1

安装OpenCV

注意之前选择的OpenCV2.4.11版本对CUDA8.0是不兼容的,要升级到OpenCV2.4.13

cd ~/opencv
mkdir release
cd release
cmake -D CMAKE_BUILD_TYPE=RELEASE -D CMAKE_INSTALL_PREFIX=/usr/local -D CUDA_GENERATION=Kepler ..
make -j8
sudo make install

因为OpenCV在cmake的时候找不到anaconda的python路径,所以需要手动将cv2.so和cv.py复制到anaconda对应的lib下。注意安装完之后再编译与OpenCV相关程序会出现报错cannot find -lopencv_dep_cudart。解决办法是要控制CMake的一个开关,在cmake的时候注意

cmake -D CUDA_USE_STATIC_CUDA_RUNTIME=OFF ..

或者在CMakeLists.txt中在find_package(OpenCV REQUIRED)前添加

set(CUDA_USE_STATIC_CUDA_RUNTIME OFF)

但这样还是需要更改每个工程的配置,还是有些麻烦,我采用了大杀器,更改了cmake安装包里的默认配置。使用的是cmake3.7.0这个安装包。

vim ./Modules/FindCUDA.cmake
# line 793, ON --> OFF
option(CUDA_USE_STATIC_CUDA_RUNTIME "Use the static version of the CUDA runtime library if available" OFF)

注意官网日志说明OpenCV3.2版本支持CUDA8,如果硬着头皮上OpenCV3.2,安装也没啥多说的。注意k80的GPU是kepler架构,如果是pascal架构的也要相应选择。在安装的时候还会遇到Downloading ippicv_linux_20151201.tgz...的问题,要确保服务器能访问raw.githubusercontent.com。还有一个bug是mkl也要更新到1.13新版本,不然会报错缺少mkl_version.h文件。

安装tensorflow

新版本tensorflow安装已经非常简单了,只需要直接pip安装即可。相应的依赖包都会自动更新到匹配版本,非常方便

pip install tensorflow-gpu    # gpu version
pip install tensorflow    # cpu version

tips

安装好之后可能会遇到can’t find libmkl_avx.so的问题,应该是anaconda和tensorflow的安装顺序导致的,可以通过下面的办法暂时解决

export LD_PRELOAD=libmkl_rt.so

安装bazel

安装jdk8

bazel需要java8的支持,由于系统本身有一套jdk7的环境,我这套jdk8的环境只想对我生效,配置如下:

download jdk-8u131-linux-x64.tar.gz
tar xzvf jdk-8u131-linux-x64.tar.gz
vim ~/.bashrc
    export JAVA_HOME=$HOME/software_bak/jdk1.8.0_131
    export PATH=$JAVA_HOME/bin:$PATH
    export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
source ~/.bashrc
java -version

安装bazel

wget https://github.com/bazelbuild/bazel/releases/download/0.5.1/bazel-0.5.1-installer-linux-x86_64.sh
chmod +x bazel-0.5.1-installer-linux-x86_64.sh
./bazel-0.5.1-installer-linux-x86_64.sh --user
vim ~/.bashrc
    export PATH=$PATH:$HOME/bin
source ~/.bashrc
bazel version

源码安装tensorflow

git    clone --recurse-submodules https://github.com/tensorflow/tensorflow
cd tensorflow
./configure // 大部分都使用默认选项,注意GPU选项要打开
bazel build -c opt --config=cuda //tensorflow/tools/pip_package:build_pip_package
bazel-bin/tensorflow/tools/pip_package/build_pip_package /tmp/tensorflow_pkg
pip install /tmp/tensorflow_pkg/tensorflow-0.10.0-py2-none-any.whl

用CMake搭建一个高质量的C/C++工程

准备工作

  • 将工程c源码放入子文件夹src
  • 将工程头文件放入子文件夹include
  • 将说明文档放入子文件夹doc
  • 在根目录创建COPYRIGHT、README、sh启动脚本

常用关键命令

定义工程名称,定义好会预定义两个工程变量PROJECT_BINARY_DIRPROJECT_SOURCE_DIR

PROJECT(projectname [CXX] [C])

显式定义变量

SET(VAR [VALUE])
SET(SRC_LIST main.c func1.c)     # eg.

终端输出,printf功能,其中SEND_ERROR表示出错信息,生成过程跳过,STATUS输出前缀为-的信息,FATAL_ERROR会终止程序

MESSAGE([SEND_ERROR |STATUS | FATAL_ERROR] "message to display" ...)

定义工程生成可执行文件

ADD_EXECUTABLE(hello ${SRC_LIST})

定义工程生成库文件

ADD_LIBRARY(libname [SHARED|STATIC|MODULE] ${SRC_LIST})

将src子目录加入工程

ADD_SUBDIRECTORY(source_dir [binary_dir])

定义工程头文件目录

INCLUDE_DIRECTORIES([AFTER|BEFORE][SYSTEM] ${INC_DIR})

链接动态库

链接动态库名称(gcc中的-l)

TARGET_LINK_LIBRARIES(target library1 <debug | optimized> library2 ...)

链接时路径ling-time path(gcc中的-L)

LINK_DIRECTORIES(${LIB_DIR})

运行时路径run-time path(gcc中的-rpath)

set(CMAKE_SKIP_BUILD_RPATH FALSE)    # 决定编译时是否添加rpath信息
set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE)    # 使编译安装使用同一rpath
set(CMAKE_INSTALL_RPATH $ORIGIN) # ORIGIN为当前目录,很有用的一个宏
set(CMAKE_INSTALL_RPATH_USE_LINK_PATH FALSE) # 设为FALSE可以将编译和运行时的路径分开

查看链接库信息命令

  • ldd列出动态库依赖信息
  • readelf -d查看动态库的信息
  • file查看文件信息

常用环境变量

PROJECT_BINARY_DIR  # 工程生成目录(通常是当前目录)
PROJECT_SOURCE_DIR  # 工程根目录
SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)
CMAKE_INSTALL_PREFIX  # 配置安装路径,默认定义是/usr/local

OpenCV相关技巧

find_package(OpenCV REQUIRED)
target_link_libraries(target ${OpenCV_LIBS})

caffe学习(八)添加模块并JNI移植

添加识别代码

examples文件夹下面新建文件夹porno_recognition,并添加cpp源码。这里从caffe/examples/cpp_classification/classification.cpp中复制了一段识别的代码,并修改classify函数以及添加load函数。由于要生成动态链接库,所以把main函数屏蔽掉。

修改Makefile

找到$(EXAMPLE_BINS): %.bin : %.o | $(DYNAMIC_NAME)部分,添加-fPIC -shared,重新make all,编译生成 build/examples/porno_recognition/porno_recognition.bin,可以看到这个新生成的bin比之前的小,确认其为动态链接库的模型,执行mv porno_recognition.bin libSuningPore.so

JNI

添加java接口函数

创建文件夹com\suning\pore并在其目录下面添加SuningPore.java,代码如下:

package com.suning.pore;

public class SuningPore
{
    static
    {        
        System.loadLibrary("SuningPore");
        System.out.println("***载入libSuningPore.so完成***");
    }

    public native static int load(String ocrDataPath, String libPath, String logPath);
    public native static String start(String imgUrl, byte[] pic);
}

这其中的loadLibrary函数用来加载libSuningPore.so动态链接库。load和start两个函数是这个动态链接库提供的两个接口函数。

创建.h文件

在com的上一级目录下,执行

javah com.suning.pore.SuningPore

生成文件com_suning_pore_SuningPore.h

创建cpp接口实现代码

创建com_suning_pore_SuningPore.cpp,并实现对应头文件中定义的两个接口函数的实现。并将之前实现的porno_recognition.cpp与该文件整合为一个文件

#include "com_suning_pore_SuningPore.h"
#include <string>

int jni_load(const char *model_path, const char* lib_path, const char *log_path);

std::string jni_start(const char* imgUrl, const unsigned char *p_imgdata, int data_size);

JNIEXPORT jint JNICALL Java_com_suning_pore_SuningPore_load
(JNIEnv *env, jclass obj, jstring model_path, jstring lib_path, jstring log_path)
{
    const char *model_string = (env)->GetStringUTFChars(model_path, 0);
    const char *lib_string = (env)->GetStringUTFChars(lib_path, 0);
    const char *log_string = (env)->GetStringUTFChars(log_path, 0);

    jint ret = jni_load(model_string, lib_string, log_string);

    (env)->ReleaseStringUTFChars(model_path, model_string);
    (env)->ReleaseStringUTFChars(log_path, log_string);
    (env)->ReleaseStringUTFChars(lib_path, lib_string);

    return ret;
}

JNIEXPORT jstring JNICALL Java_com_suning_pore_SuningPore_start
(JNIEnv *env, jclass obj, jstring img_url, jbyteArray in_buf)
{
    const char *url_string = (env)->GetStringUTFChars(img_url, 0);

    jbyte *cbuf;
    cbuf = (env)->GetByteArrayElements(in_buf, 0);
    jsize in_len = (env)->GetArrayLength(in_buf);

    const unsigned char *p_imgdata = (unsigned char*)cbuf;
    int data_size = in_len;

    jstring ret = 0;
    if (p_imgdata == NULL || data_size <= 0)
    {
        return ret;
    }

    const std::string out_str = jni_start(p_imgdata, data_size);

    (env)->ReleaseStringUTFChars(img_url, url_string);
    (env)->ReleaseByteArrayElements(in_buf, cbuf, 0);

    if (!out_str.empty()) {
        ret = (env)->NewStringUTF(out_str.c_str());
    }
    return ret;
}

然后将com_suning_pore_SuningPore.cppcom_suning_pore_SuningPore.h放入caffe/examples/porno_recognition文件夹中,修改Makefile.config的中加入java的include,重新编译生成com_suning_pore_SuningPore.bin(注意:该文件实际上是一个动态链接库,不可直接执行)。

### Makefile.config ###
# INCLUDE_DIRS := ...
    /usr/lib/jvm/java/include \
    /usr/lib/jvm/java/include/linux \

添加java测试程序

com/suning/pore下添加TestSuningPore.java并添加如下代码:

package com.suning.pore;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;

public class TestSuningPore
{
    public static void main(String[] args) throws Exception
    {
        // TODO Auto-generated method stub

        TestSuningPore test = new TestSuningPore(); 

        if (args.length == 0)
        {
            System.out.println("image file missing");
            return;
        }
        test.porno_recognize(test.readImage(args[0])); 
    }

    public TestSuningPore(){}

    private byte[] readImage(String path) throws Exception
    {
        File image = new File(path);
        FileInputStream fis = new FileInputStream(image);

        byte[] b = new byte[(int)image.length()];
        fis.read(b);
        fis.close();

        return b;
    }

    public String porno_recognize(byte[] imageByte)
    {
        if(null != imageByte)
        {
            SuningPore suning_pore = new SuningPore();
            boolean status = false;
            System.out.println("check status: " + status);

            if(!status)
            {    
                int load = suning_pore.load("/opt/pore/module/", "/opt/pore/lib/", "/opt/pore/log/");
                if(load != 1)
                {
                    System.out.println("Error: load failed!");
                    return "Error: load failed!";
                }
            }

            try
            {

                String words = suning_pore.start(imageByte);
                //String words = SuningPore.start("http://abc.efg.cn/hij.jpg", imageByte);  // this also works

                System.out.println("\nresulting words:\n" + words);

                return words;
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        return null;
    }
}

测试jni接口

进入com上一级目录,并添加run.sh,添加如下代码:

cp ~/caffe/build/examples/porno_recognition/com_suning_pore_SuningPore.bin ./libSuningPore.so
cp ~/caffe/build/lib/libcaffe.so.1.0.0-rc3 .
javac -encoding UTF8 com/suning/pore/*.java
java -Djava.library.path=.  com.suning.pore.TestSuningPore $1

通过./run.sh xxx.jpg命令测试该接口

tips

依赖动态链接库问题

由于libSuningPore.solibcaffe.so.1.0.0-rc3依赖较多的动态链接库,可能需要指定动态链接库的路径,这时候把相关的动态链接库放到指定路径中,通过/etc/profile和source命令使该路径生效,并通过/etc/ld.so.conf文件设置include /opt/xxx/xxxldconfig来使系统更新相关路径的动态链接库。然后再重新编译caffe,通过ldd命令就可以查看这两个用到的动态库确实在新路径下找到了相关依赖。
需要注意的一点是,如果使用了mkl,注意可能需要一个libiomp5.so的文件,添加到相关动态链接库里就可以了。