关于caffe2的ios编译及部署,github上已经有比较成熟的代码,这里选择基于该作者的工作进行修改部署。
https://github.com/KleinYuan/Caffe2-iOS
按照作者给出的步骤,进行git clone和编译
brew install git-lfs
git lfs install
git lfs clone https://github.com/KleinYuan/Caffe2-iOS
完成后进入src目录,调用setup.sh,将会自动下载caffe2并编译caffe2的ios库
cd Caffe2-iOS/src/caffe2-ios
bash ./setup.sh
上个步骤大概耗费20~30分钟左右。
完成后,会发现在src目录中,新建了一个caffe2的目录,里面包含caffe2原始文件和编译好的库。由于作者已经在工程中配置好了一切,所以只需要打开xcode工程进行测试即可。
打开src/caffe2-ios/caffe2-ios.xcodeproj进行编译,我这里出现错误,提示caffe2.pb.h和predictor_consts.pb.h找不到。如果上面的安装过程正常的话,应该会在src/caffe2/caffe2/proto路径下调用protobuf的protoc进行生成的,但是我上面的安装过程里跳过了这一步,没关系,可以手动生成。不管你系统是否已经安装了protobuf,建议直接使用src/caffe2/build_host_protoc/bin里的protoc进行生成,否则可能会有版本不一致的错误:
cd src/caffe2/caffe2/proto
../../build_host_protoc/bin/protoc caffe2.proto --cpp_out=./
../../build_host_protoc/bin/protoc predictor_consts.proto --cpp_out=./
生成后,重新编译xcode工程,编译成功即可运行。
demo里作者给出了两个caffe2模型:squeezenet和tinyyolo,我在iphone6sp上测时间分别为35ms/frame及52ms/frame
在上一步中,测试了作者给出的caffe2模型,但如果要将自己的caffe模型部署到ios上呢?
caffe2很方便地提供了转换的接口,在使用该接口前,需要编译安装caffe2(上一步中只是编译了caffe2的iOS库)
进入caffe2目录并新建build文件夹
cd src/caffe2
mkdir build && cd build
cmake -DUSE_CUDA=OFF ..
sudo make install
安装成功后,caffe2相关的库和python库将被安装在/usr/local中,当然可以在cmake的时候指定安装目录
将/usr/local添加到python环境变量中
export PYTHONPATH=/usr/local
完成后可以测试一下看是否有错误
cd ~ && python -c 'from caffe2.python import core' 2>/dev/null && echo "Success" || echo "Failure"
调用python接口caffe_translator转换caffe2模型
python caffe2/python/caffe_translator.py deploy.prototxt test_iter_83000.caffemodel --init_net=testinit.pb --predict_net=testpredict.pb
最终需要用到的就是转换后的模型testinit.pb和testpredict.pb
在工程中加入转换好的模型
声明caffe2对象并
@property (nonatomic, strong) Caffe2 *caffe2;
self.caffe2 = [[Caffe2 alloc] init:@"init" predict:@"predict" error:&err];
预测一副图像
NSMutableArray *ret = [self.caffe2 predictwithmat:resimg withMean: &mean_val withScale:1.f];
这里的predict接口函数是自己实现的,可以支持输入opencv图像和均值减除和归一化等预处理
- (nullable NSArray*) predictwithmat:(cv::Mat) image withMean: (float*)mean_val withScale: (float) scale
{
NSMutableArray* result = nil;
caffe2::Predictor::TensorVector output_vec;
if (self.busyWithInference) {
return nil;
} else {
self.busyWithInference = true;
}
UInt8* pixels = (UInt8*) image.data;
caffe2::TensorCPU input;
size_t w = image.cols, h = image.rows ;
// Reasonable dimensions to feed the predictor.
const int predHeight = (int)CGSizeEqualToSize(self.imageInputDimensions, CGSizeZero) ? int(h) : self.imageInputDimensions.height;
const int predWidth = (int)CGSizeEqualToSize(self.imageInputDimensions, CGSizeZero) ? int(w) : self.imageInputDimensions.width;
const int crops = 1;
const int channels = image.channels();
const int size = predHeight * predWidth;
std::vector<float> inputPlanar(crops * channels * predHeight * predWidth);
if (channels > 1) //bgr img
{
for (int c = 0; c < channels; c++)
{
for (int i = 0; i < predHeight; i++)
{
for (int j = 0; j < predWidth; j++)
{
inputPlanar[i * predWidth + j + c * size] = scale * float(image.at(i, j)[c] - mean_val[c]);
}
}
}
}
else{
for (int i = 0; i < predHeight; i++)
{
for (int j = 0; j < predWidth; j++)
{
inputPlanar[i * predWidth + j ] = scale * float(image.at(i, j) - mean_val[0]);
}
}
}
input.Resize(std::vector<int>({crops, channels, predHeight, predWidth}));
input.ShareExternalPointer(inputPlanar.data());
caffe2::Predictor::TensorVector input_vec{&input};
NSTimeInterval start = [[NSDate date] timeIntervalSince1970];
_predictor->run(input_vec, &output_vec);
NSTimeInterval end = [[NSDate date] timeIntervalSince1970];
NSLog(@"elapsed time %f" , (end-start)*1000);
if (output_vec.capacity() > 0) {
for (auto output : output_vec) {
// currently only one dimensional output supported
result = [NSMutableArray arrayWithCapacity:output_vec.size()];
for (auto i = 0; i < output->size(); ++i) {
result[i] = @(output->template data<float>()[i]);
NSLog(@"%@", result[i]);
}
}
}
self.busyWithInference = false;
return result;
}