美文网首页DeepAI
Yolo-v3目标检测—Java调用C++(JNI)

Yolo-v3目标检测—Java调用C++(JNI)

作者: Sunflow007 | 来源:发表于2020-03-08 23:09 被阅读0次
    18.jpg

    前言

    其实这篇文章重点在如何用Java的JNI调用C++的dll,记录一下,避免以后自己忘了.....
    原文发表在语雀文档上,排版更美观


    简介

    JNI—摘自百度百科
    JNI是Java Native Interface的缩写,它提供了若干的API实现了Java和其他语言的通信(主要是C&C++)。从Java1.1开始,JNI标准成为java平台的一部分,它允许Java代码和其他语言写的代码进行交互。JNI一开始是为了本地已编译语言,尤其是C和C++而设计的,但是它并不妨碍你使用其他编程语言,只要调用约定受支持就可以了。使用java与本地已编译的代码交互,通常会丧失平台可移植性。但是,有些情况下这样做是可以接受的,甚至是必须的。例如,使用一些旧的库,与硬件、操作系统进行交互,或者为了提高程序的性能。JNI标准至少要保证本地代码能工作在任何Java 虚拟机环境。

    由于近期在玩yolo、darknet,C++项目下的图像识别(目标检测),想尝试下将生成的dll提供给Java服务端调用,于是就有了本篇文章~记个流水账怕以后自己忘了....


    流程

    整体来说,要直接将dll被java调用是不可能的,因为两种语言基本数据类型、方法定义这些是不同的,所以需要用VS新建一个dll项目,生成java项目中可调用的dll。

    1.新建native接口方法类

    在Java项目中任意位置新建一个类,声名需要用到的native方法,凡是用native修饰的方法,都是后面调用的dll中的方法(C++实现),static块中System.load方法即可实现加载dll,在刚开始这部分可以忽略不写,等VS生成dll后再过来添加。
    DarknetJavaSDK.java

    package com.xxx.ai.image.detection.service.sdk;
    
    import java.io.File;
    
    public class DarknetJavaSDK {
    
        public native String get_version();
    
        public native boolean set_logfile_path(String logPath);
    
        public native boolean load_model(String cfgPath, String modelPath);
    
        public native int detect_image(String imagePath, String outDirPath);
    

    2.生成.h头文件

    生成头文件时,因为DarknetJavaSDK.java文件从属于包:
    package com.xxx.ai.image.detection.service.sdk;所以,需要cd到.../src/main/java目录下(即com/xxx/ai的上一级目录),运行:
    javah com.xxx.ai.image.detection.service.sdk.DarknetJavaSDK
    即可在当前目录下生成.h文件:com_xxx_ai_image_detection_service_sdk_DarknetJavaSDK.h

    特别注意:如果当前类:DarknetJavaSDK.java 中有依赖其他你自定义的Java类,则可能报错,因为类加载的路径中找不到。解决方法:指定-classpath到.../src/main/java目录下,这样即可加载到此路径下com包下的所有依赖类
    例如:javah -classpath D:\personalProject\AI\image\detection\src\main\java com.flowingbit.ai.image.detection.service.sdk.DarknetJavaSDK

    /* DO NOT EDIT THIS FILE - it is machine generated */
    #include <jni.h>
    /* Header for class com_xxx_ai_image_detection_service_sdk_DarknetJavaSDK */
    
    #ifndef _Included_com_xxx_ai_image_detection_service_sdk_DarknetJavaSDK
    #define _Included_com_xxx_ai_image_detection_service_sdk_DarknetJavaSDK
    #ifdef __cplusplus
    extern "C" {
    #endif
    /*
     * Class:     com_xxx_ai_image_detection_service_sdk_DarknetJavaSDK
     * Method:    get_version
     * Signature: ()Ljava/lang/String;
     */
    JNIEXPORT jstring JNICALL Java_com_xxx_ai_image_detection_service_sdk_DarknetJavaSDK_get_1version
      (JNIEnv *, jobject);
    
    /*
     * Class:     com_xxx_ai_image_detection_service_sdk_DarknetJavaSDK
     * Method:    set_logfile_path
     * Signature: (Ljava/lang/String;)Z
     */
    JNIEXPORT jboolean JNICALL Java_com_xxx_ai_image_detection_service_sdk_DarknetJavaSDK_set_1logfile_1path
      (JNIEnv *, jobject, jstring);
    
    /*
     * Class:     com_xxx_ai_image_detection_service_sdk_DarknetJavaSDK
     * Method:    load_model
     * Signature: (Ljava/lang/String;Ljava/lang/String;)Z
     */
    JNIEXPORT jboolean JNICALL Java_com_xxx_ai_image_detection_service_sdk_DarknetJavaSDK_load_1model
      (JNIEnv *, jobject, jstring, jstring);
    
    /*
     * Class:     com_xxx_ai_image_detection_service_sdk_DarknetJavaSDK
     * Method:    detect_image
     * Signature: (Ljava/lang/String;Ljava/lang/String;)I
     */
    JNIEXPORT jint JNICALL Java_com_xxx_ai_image_detection_service_sdk_DarknetJavaSDK_detect_1image
      (JNIEnv *, jobject, jstring, jstring);
    
    #ifdef __cplusplus
    }
    #endif
    #endif
    
    

    4.VS新建dll项目

    我的项目:DarknetDllForJava

    image

    添加必须的头文件:

    jni.h
    com_xxx_ai_image_detection_service_sdk_DarknetJavaSDK.h

    jni.h通常在jdk的include目录下,如我的:C:\Program Files\Java\jdk1.8.0_151\include

    VC++目录下设置包含路径:

    jni.h依赖的路径:
    C:\Program Files\Java\jdk1.8.0_151\include
    C:\Program Files\Java\jdk1.8.0_151\include\win32

    image

    在DarknetDllForJava.cpp定义.h的导出函数

    // DarknetDllForJava.cpp : 定义 DLL 应用程序的导出函数。
    //
    
    #include "stdafx.h"
    #include "com_xxx_ai_image_detection_service_sdk_DarknetJavaSDK.h"
    #include "dll_api.h"
    
    JNIEXPORT jstring JNICALL Java_com_xxx_ai_image_detection_service_sdk_DarknetJavaSDK_get_1version(JNIEnv *env, jobject obj) {
        return get_version(env);
    }
    
    JNIEXPORT jboolean JNICALL Java_com_xxx_ai_image_detection_service_sdk_DarknetJavaSDK_set_1logfile_1path(JNIEnv *env, jobject obj, jstring logPath) {
        return set_logfile_path(env, logPath);
    }
    
    JNIEXPORT jboolean JNICALL Java_com_xxx_ai_image_detection_service_sdk_DarknetJavaSDK_load_1model(JNIEnv *env, jobject, jstring cfgPath, jstring modelPath) {
        return load_model(env, cfgPath, modelPath);
    }
    
    JNIEXPORT jint JNICALL Java_com_xxx_ai_image_detection_service_sdk_DarknetJavaSDK_detect_1image(JNIEnv *env, jobject, jstring imagePath, jstring outDirPath) {
        return detect_image(env, imagePath, outDirPath);
    }
    
    

    新建导出函数头文件dll_api.h

    #pragma once
    #ifndef DLL_API_H
    #define DLL_API_H
    
    jstring get_version(JNIEnv *env);
    
    jboolean set_logfile_path(JNIEnv *env, jstring logPath);
    
    jboolean load_model(JNIEnv *env, jstring cfgPath, jstring modelPath);
    
    int detect_image(JNIEnv *env, jstring imagePath, jstring outDirPath);
    
    #endif
    
    

    新建dll_api.cpp,定义函数实现

    这里就是按照dll_api.h里的函数定义,编写其实现,需要注意的是,需要添加#include。
    然后,Java中的基本数据类型和C++中的有些是需要相互转化的,如:
    jboolean表示java中的布尔值true和false,在c++中对应的是JNI_FALSE和JNI_TRUE;
    jstring表示java中的String类,jstring和c++中的string类的相互转化可以用以下函数:

    jstring str2jstring(JNIEnv* env, const char* pat)
    {
        //定义java String类 strClass
        jclass strClass = (env)->FindClass("Ljava/lang/String;");
        //获取String(byte[],String)的构造器,用于将本地byte[]数组转换为一个新String
        jmethodID ctorID = (env)->GetMethodID(strClass, "<init>", "([BLjava/lang/String;)V");
        //建立byte数组
        jbyteArray bytes = (env)->NewByteArray(strlen(pat));
        //将char* 转换为byte数组
        (env)->SetByteArrayRegion(bytes, 0, strlen(pat), (jbyte*)pat);
        // 设置String, 保存语言类型,用于byte数组转换至String时的参数
        jstring encoding = (env)->NewStringUTF("GB2312");
        //将byte数组转换为java String,并输出
        return (jstring)(env)->NewObject(strClass, ctorID, bytes, encoding);
    }
    
    string jstring2str(JNIEnv* env, jstring jstr)
    {
        char*   rtn = NULL;
        jclass   clsstring = env->FindClass("java/lang/String");
        jstring   strencode = env->NewStringUTF("GB2312");
        jmethodID   mid = env->GetMethodID(clsstring, "getBytes", "(Ljava/lang/String;)[B");
        jbyteArray   barr = (jbyteArray)env->CallObjectMethod(jstr, mid, strencode);
        jsize   alen = env->GetArrayLength(barr);
        jbyte*   ba = env->GetByteArrayElements(barr, JNI_FALSE);
        if (alen > 0)
        {
            rtn = (char*)malloc(alen + 1);
            memcpy(rtn, ba, alen);
            rtn[alen] = 0;
        }
        env->ReleaseByteArrayElements(barr, ba, 0);
        std::string stemp(rtn);
        free(rtn);
        return   stemp;
    }
    
    

    5.生成dll,并在Java中加载

    第4.步骤完成后,生成的dll时可以直接被java加载利用的,只需要在DarknetJavaSDK.java中用
    System.load(YOUR_DLL_PATH);即可完成dll加载工作,顺序不正常会报错。。。
    以我的为例:
    DarknetDllForJava.dll是第4.步新建的dll项目生成的dll,其运行依赖上面三个dll,所以其顺序放在最后。

    image

    然后再添加@Service注解,让其可以作为一个service被Autowired,改造后的DarknetJavaSDK.java:

    package com.xxx.ai.image.detection.service.sdk;
    import org.springframework.stereotype.Service;
    import java.io.File;
    @Service
    public class DarknetJavaSDK {
    
        private static final String OPENCV_WORLD340_DLL = "opencv_world340.dll";
        private static final String PTHREADVC2_DLL = "pthreadVC2.dll";
        private static final String YOLO_DLL_CPU_REALEASE_DLL = "yolo_dll_cpu_r.dll";
        private static final String DARKNETDLL_FOR_JAVA_DLL = "DarknetDllForJava.dll";
    
        static{
            StringBuilder sb = new StringBuilder(System.getProperty("user.dir")).append(File.separator).append("dll").append(File.separator);
            final String dirPath = sb.toString();
            System.load(dirPath + OPENCV_WORLD340_DLL);
            System.load(dirPath + PTHREADVC2_DLL);
            System.load(dirPath + YOLO_DLL_CPU_REALEASE_DLL);
            System.load(dirPath + DARKNETDLL_FOR_JAVA_DLL);
        }
    
        public native String get_version();
    
        public native boolean set_logfile_path(String logPath);
    
        public native boolean load_model(String cfgPath, String modelPath);
    
        public native int detect_image(String imagePath, String outDirPath);
    

    效果演示:

    Java接口调用本地方法:detect_image()

    image

    返回检测出的目标数量20、在指定dirPath下生成检测图片:

    image

    整个过程参考过如下文章:
    https://blog.csdn.net/qq_38288172/article/details/82387946
    https://www.jb51.net/article/132930.htm
    https://www.cnblogs.com/haitaofeiyang/p/7698121.html

    相关文章

      网友评论

        本文标题:Yolo-v3目标检测—Java调用C++(JNI)

        本文链接:https://www.haomeiwen.com/subject/epzhdhtx.html