背景
最近產品需要做一個物體識別的app demo, 咨詢研究人員之后,得到開源的yolo9000可以滿足需要,yolo中使用的darknet是C語言編寫的,yolo9000編譯之后本身提供了命令行模式來生成識別結果,默認的結果是識別后帶有畫框的圖片,如圖:

圖片中框體title即是識別的結果(只有英文),首先想到的是可以通過java執行本地命令的方式來生成圖片,然后將圖片以接口的方式傳給app,但是app拿到圖片后就只能直接展示給用戶,無法再做如翻譯等進一步的處理。
基於以上情況,我想到的解決方案是:對darknet源碼進行改寫,添加識別物體返回json數據(包含物體名稱,坐標,識別百分比等)的函數,再利用Java可以調用本地函數的特性直接調用該函數。
實現過程
JAVA調用C方法獲取識別結果,思路是這樣的:將用戶傳來的圖片放到一個臨時目錄中,然后調用C函數分析,得到結果后,返回給用戶。所以先定義一個調用C得本地方法
package com.iflytek.research.yoloserver;
/**
* 對yolo9000的封裝
* <p>調用本地庫來識別圖片中的物體</p>
* @author ljgeng
*
*/
public class Yolo {
/**
* 物體識別,函數會從指定的路徑讀取圖片解析
* @param imgPath 圖片的路徑
* @return 識別的結果,json 格式的文本
*/
public static native String predict(String imgPath);
}
定義好函數之后,利用javah 工具自動生成c語言的頭文件。
javah com.iflytek.research.yoloserver.Yolo
運行后會生成一個com_iflytek_research_yoloserver_Yolo.h 文件,將文件導入C項目中
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_iflytek_research_yoloserver_Yolo */
#ifndef _Included_com_iflytek_research_yoloserver_Yolo
#define _Included_com_iflytek_research_yoloserver_Yolo
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_iflytek_research_yoloserver_Yolo
* Method: predict
* Signature: (Ljava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_iflytek_research_yoloserver_Yolo_predict
(JNIEnv *, jclass, jstring);
#ifdef __cplusplus
}
#endif
#endif
在C項目中新建yolo.c, 引入剛剛的頭文件,並實現predict方法
#include "com_iflytek_research_yoloserver_Yolo.h"
#include "stdio.h"
JNIEXPORT jstring JNICALL Java_com_iflytek_research_yoloserver_Yolo_predict
(JNIEnv * env, jclass jcs, jstring jstr){
const char * str = (*env)->GetStringUTFChars(env,jstr,0);
if (str == NULL) {
return NULL;
}
printf("%s!\n",str);
(*env)->ReleaseStringUTFChars(env, jstr, str);
char * jsonStr = "{\"semantic\":{\"slots\":{\"name\":\"張三\"}}, \"rc\":0, \"operation\":\"CALL\", \"service\":\"telephone\", \"text\":\"打電話給張三\"}";
return (*env)->NewStringUTF(env, jsonStr);
}
現在只是先跑通流程,所以在yolo.c中還沒有真正調用object detection相關的方法,以下對predict函數的簡單解釋
const char * str = (*env)->GetStringUTFChars(env,jstr,0); // 調用jni 函數GetStringUTFChars 讀取Java String 對象內容
printf("%s!\n",str); // 打印
return (*env)->NewStringUTF(env, jsonStr); // 調用jni函數NewStringUTF 返回一個Java String對象。
JNI 有不少函數,有興趣可以去官網或者相關博客學習一下。
寫好C代碼之后,將其編譯到動態庫中,供Java調用,我使用的是window系統,於是安裝了cygwin64,並帶上gcc功能。
x86_64-w64-mingw32-gcc.exe -D __int64="long long" -I "C:\Program Files\Java\jdk1.8.0_151\include" -I "C:\Program Files\Java\jdk1.8.0_151\include\win32" -shared -o yolo.dll yolo.c -W
具體使用哪個gcc命令,看系統實際情況。成功后,生成的yolo.dll 拷貝到Java項目根目錄,加載庫后運行。
package com.iflytek.research.yoloserver;
/**
* 程序入口
*
*/
public class YoloServerApp {
static {
System.loadLibrary("yolo");
}
public static void main(String[] args) {
String re = Yolo.predict("你好");
System.out.println(re);
}
}
使用靜態代碼塊先加載庫,然后運行predict函數,成功返回了json字符串。
