JAVA調用C語言函數的封裝過程


背景

最近產品需要做一個物體識別的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字符串。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM