上篇文章实现了Qt+MinGW+Opencv+Zbar的配置。接下来,由于项目需求,需要用Java调用,因此需要将之前二维码识别的代码编译成Dll,供java调用。
1 Java调用Dll的方法
1.1 利用Java自带的JNI
JNI是Java Native Interface的缩写,通过使用 Java本地接口书写程序,可以确保代码在不同的平台上方便移植。它允许Java代码和其他语言写的(本地已编译的)代码进行交,这样做通常会丧失平台可移植性。但是,有些情况下这样做是可以接受的,甚至是必须的。
基本流程是首先在java环境下,建立一个java的接口,然后利用Java自带的工具javah,将这个接口转换成C或C++类型的接口,然后,在VC或中Qt的环境下借助编译器,实现这个接口的功能,并编译成Dll。 在Java环境下通过调用这个Dll,就可以访问本地代码或已编译的库的功能,这个方法的效率是最高的,缺点是由于对应于某一平台的 JNI 本地代码调用通常不能移植到其他平台上。
这种方法适用于核心代码大部分已经在本地完成,将代码重新写成Java的工作量复杂或者根本无法完成的情况,我们需要在本地用C++或C把这些代码封装起来供Java 使用,这个封装的Dll可以由我们指定实现某种功能,也就是说可以在保证Java代码不更改的情况下,改变程序的功能。
1.2 利用Java自带的JNA
JNA(Java Native Access )是建立在JNI技术基础之上的一个Java类库,它使您可以方便地使用java直接访问动态链接库中的函数,整个过程均在Java环境下完成。
基本流程是在一个java接口中描述本地库(native library)中的函数与结构,JNA将自动实现Java接口到native function的映射,避免了像JNI一样手工用C或C++写一个动态链接库,也许这也意味着,使用JNA技术比使用JNI技术调用动态链接库会有些微的性能损失。范例代码:
这种方法适用于核心代码大部分已经用Java完成,为了直接调用已经存在的Dll的功能,我们只需要查看Dll的函数结构,利用JNA提供的Libaray接口建立一个映射,供Java 使用,但是我们无法改变Dll已经实现的代码,只能调用,因此,如果需要改变程序功能,我们必须更改Java代码。同时,向Java提供数量较少的接口时更适合这种方法。
1.3 利用第三方包JNative
JNative 与JNA有些类似,可以让你方便的访问 Windows 平台下的 DLL 以及 Linux 平台下的 so 动态连接库文件,无需在编写一行 C/C++ 代码。
基本流程是直接使用JNative载入一个Dll,返回一个静态指针,这个指针可以看作是Dll的入口,然后利用这个JNative指针,设置参数并调用Dll中的函数实现某种功能,范例代码:
与JNA类似,这种方法适用于核心代码大部分已经在Java完成,为了直接调用已经存在的Dll的功能,我们只需要查看Dll的函数结构,利用JNative载入Dll,供Java 使用,但是我们无法改变Dll已经实现的代码,只能调用,因此,如果需要改变程序功能,我们必须更改Java代码。
2 利用Java JNI声明接口
通过以上学习,我们选择采用第一种情况,原因是为了实现二维码扫描,需要借助OpenCV的库文件读取图片,以及Zbar的库文件进行扫描,我们已经在Qt环境下实现,而且对这段代码已经很熟悉(输入图片地址,输出二维码内容),另外,我们只需要像Java提供一个接口供其使用。如果借助JNA或者JNative,需要重新利用Java调用Opencv和Zbar的库文件重写整个流程,因此,对我们而言,利用JNI为Opencv和Zbar的库文件整体打包一个Dll供Java使用的代码量是最小的,同时一个优点是Java代码不改动的情况下,依然可以更新DLl提供更多的功能
2.1 安装32位JDK
由于我们在QT下调试的OpenCv和Zbar库都是32位的,因此,需要安装32位的JDK建立一个32位的Dll去调用。原本MyEclipse自带的64位JDK可以不用管,直接安装jdk-6u45-windows-i586.exe 这是一个32位的JDK,安装好以后,环境变量中,进行设置,注意此时需要删除之前的64位JDK的环境变量。
2.2 在MyEclipse下添加JRE环境
MyEclipse->Windows->Preferences->Java->Installed 选项卡:
点击Add添加Jre运行环境,选择Standard VM
然后填写JRE位置:
2.3 利用JNI建立一个本地接口
MyEclipse新建一个空的Java Application项目,新建一个JNI4Zbar类文件,如下所示:
public class JNI4Zbar {
{
System.load("E:\\QTWork\\JNI4Zbar\\JNI4Zbar\\JNI4Zbar.dll");
}
public native String scanimage(String name);
public static void main(String[] args) {
String strContent="";
//建立一个JNI4Zbar类载入dll,并调用scaniamge函数
strContent = new JNI4Zbar().scanimage("E:\\myqrcode.jpg ");
//输出扫码结果
System.out.("java QR_Data : "+strContent);
}
}
此时,注意配置好项目的Build Path,在项目上点击右键->Build Path->Configure Build Path-> Library 选项卡
选择 JRE System Library
确保选择的是32 位 JRE 1.6
同时注意查看,Java Compiler选项卡,保证是1.6版本
在命令行下编译
最终会得到:
这个JNI4Zbar.h 内容如下:
这是我们利用Javah生成的.h文件,只需要知道这个头文件通过 JNIEXPORT声明了一个接口,通过JNICALL 声明了一个调用的函数,注意这个代码不需要修改!
至此,Java的部分就已经完成,我们已经转换到C或(C++)语言平台上了,接下来将利用QT 实现.h文件声明的函数。
3 利用Qt编写接口实现
在Qt Creator中建立一个Qt Console Application项目,名称为 JNI4Zbar,然后把项目中的main.cpp 更名为 JNI4Zbar.cpp, 同时将JNI4Zbar.h文件,Jni.h 和 Jni_md.h 靠拷贝到 项目的源码中:
更改JNI4Zbar.cpp内容为
#include "JNI4Zbar.h"
#include <windows.h>
#include <iostream>
#include <jni.h>
#include <zbar.h>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
//#define STR(s) #s
using namespace std;
using namespace zbar;
using namespace cv;
JNIEXPORT jstring JNICALL Java_JNI4Zbar_scanimage(JNIEnv *env, jobject jobj, jstring jstr){
//获取传进来的文件路径,将jstring转为const char *
const char *str = env->GetStringUTFChars(jstr, 0);
//将const char *转为string
string fullpathfilename = str;
//printf("%s", str);
//env->ReleaseStringUTFChars(jstr, str);
if (str == NULL) {
return env->NewStringUTF("");
}
//建立一个zbar中的扫描器
ImageScanner scanner;
scanner.set_config(ZBAR_NONE, ZBAR_CFG_ENABLE, 1);
//读取并显示图像
Mat image = imread(fullpathfilename);
namedWindow("Source Image");
imshow("Source Image", image);
//读取图像, 参数0, 直接将图片读取为灰度
Mat imageGray = imread(fullpathfilename,0);
namedWindow("Gray Image");
imshow("Gray Image",imageGray);
waitKey();
//为Zbar建立一个图像码对象
int width = imageGray.cols;
int height = imageGray.rows;
uchar *raw = (uchar *)imageGray.data;
Image imageZbar(width, height, "Y800", raw, width * height);
//识别图像码
scanner.scan(imageZbar); //扫描条码
Image::SymbolIterator symbol = imageZbar.symbol_begin();
//输出识别信息
if(imageZbar.symbol_begin()==imageZbar.symbol_end())
{
cout<<"Bar code query failed, please check the picture!"<<endl;
}
for(;symbol != imageZbar.symbol_end();++symbol)
{
cout<<"type:"<<endl<<symbol->get_type_name()<<endl<<endl;
cout<<"bar:"<<endl<<symbol->get_data()<<endl<<endl;
}
//获取图像码中的信息
string information = symbol->get_data();
//将string 转换为 const char *
const char * return_information = information.c_str();
//清空图像码对象
imageZbar.set_data(NULL,0);
//返回获取的字符串
return env->NewStringUTF(return_information);
}
配置.pro 文件
QT -= gui
CONFIG += c++11 console
CONFIG -= app_bundle
CONFIG += shared
# The following define makes your compiler emit warnings if you use
# any feature of Qt which as been marked deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS
# You can also make your code fail to compile if you use deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
SOURCES += \
JNI4Zbar.cpp
HEADERS += \
JNI4Zbar.h \
jni_md.h \
jni.h
##################### opencv
INCLUDEPATH += C:/opencv/build32/include
DEPENDPATH += C:/opencv/build32/include
# debug header
INCLUDEPATH += C:/opencv/build32d/include
DEPENDPATH += C:/opencv/build32d/include
# dll.a lib
win32:CONFIG(release, debug|release): LIBS += C:\opencv\build32\x86\mingw\bin\libopencv_*.dll
else:win32:CONFIG(debug, debug|release): LIBS += C:\opencv\build32d\x86\mingw\bin\libopencv_*d.dll
##################### zbar
win32: LIBS += 'C:\Program Files (x86)\ZBar\bin\libzbar-0.dll'
INCLUDEPATH += 'C:\Program Files (x86)\ZBar\include'
DEPENDPATH += 'C:\Program Files (x86)\ZBar\include'
##################### jni
INCLUDEPATH += C:\Program Files (x86)\Java\jdk1.6.0_45\include
DEPENDPATH += C:\Program Files (x86)\Java\jdk1.6.0_45\include
##################### jni_md.h
INCLUDEPATH += C:\Program Files (x86)\Java\jdk1.6.0_45\include\win32
DEPENDPATH += C:\Program Files (x86)\Java\jdk1.6.0_45\include\win32
然而编译会出现错误:
我找了很多资料,一直没有解决这个'[email protected]'的问题。幸运的是我查到,可以避免利用qmake,而是直接利用g++,来编译这个文件
在JNI4Zbar.cpp的目录下,执行命令:
g++ -Wl,--kill-at -I"C:\Program Files (x86)\Java\jdk1.6.0_45\include" -I"C:\Program Files (x86)\Java\jdk1.6.0_45\include\win32" -I"C:\Program Files (x86)\ZBar\include" -I"C:\opencv\build32\include" -L"C:\Program Files (x86)\ZBar\bin" -l"libzbar-0" -L"C:\opencv\build32\x86\mingw\bin" -l"libopencv_calib3d310" -l"libopencv_core310" -l"libopencv_features2d310" -l"libopencv_flann310" -l"libopencv_highgui310" -l"libopencv_imgcodecs310" -l"libopencv_imgproc310" -l"libopencv_ml310" -l"libopencv_objdetect310" -l"libopencv_photo310" -l"libopencv_shape310" -l"libopencv_stitching310" -l"libopencv_superres310" -l"libopencv_video310" -l"libopencv_videoio310" -l"libopencv_videostab310" -l"opencv_ffmpeg310" -shared -o JNI4Zbar.dll JNI4Zbar.cpp
其中参数 -Wl,--kill-at 是让函数名称不带@符号 -I 是包含的include目录,-L是需要的动态链接库的目录 -l后面紧跟的是需要的动态链接库的名称(注意不包含.dll) -shared 表示创建的是动态链接库 -o 后面是输出的dll的名称,最后的参数 JNI4Zbar.cpp就是编译的cpp文件,编译完后会生成一个JNI4Zbar.dll文件。
需要强调的是,这里的zbar用的是0.1.0版本exe安装包zbar-0.10-setup.exe,自带32位libzbar-0.dll。OpenCV32位的dll库上一篇文章中,qt+MinGW编译的,这里应该需要确认环境变量是否配置正确:
我这里用的是笨方法,把每一个用到的头文件和库文件全都在命令行里面,应该有简单的写法吧,实际上qt的qmake应该就是通过.pro文件中的配置来编译的,但是不知道为什么通不过,还好这种方法能编译成功。如果你是编译c文件,需要用gcc 而不是 g++。
4 在Java环境下测试
重新回到Java环境中,运行JNI4Zbar
运行时会出现:
按任意按键,则关闭二维码窗口,并继续运行,结果如下:
由此,我们实现了在利用JNI和g++编译dll,这样java程序中可以调用zbar实现二维码识别了