本文最后更新于 over 3 years ago,文中所描述的信息可能已发生改变。
前言
为了将halcon编写的算法用在WEB端的产品,需要导出为C++代码,并在Linux打包成SO库,最终由JAVA的JNA调用…在折腾了一天后解决连“高级JAVA工程师”都无法解决的问题了↘呢→
我 们 联 合 !
正文
先放参考链接,因为之前没打包,思路也大多时从网上找的:
【1】超详细:halcon 与 VS 联合编程教程 - 知乎 【2】使用VS生成本地静态库文件以及静态库文件的使用_WHEgqing的专栏-CSDN博客 【3】Ubuntu18.04+Halcon18.11安装教程 【4】linux下java调用.so动态库方法
文章以文字为主(主要是贴图太麻烦),我尽量把操作步骤写明白;
用来示范的算法是 二维码识别,halcon代码如下:
* main文件
action ('123.png', result)
------分割线------
* action(::path:result)
read_image (Image, path)
**输入图像
**输出二维码的结果
get_2dcode_result (Image, DecodedDataStrings)
result := DecodedDataStrings
return ()
------分割线------
* get_2dcode_result(tdcode:::DecodedDataStrings)
* *输入二维码区域的图像变量Image
* *输出二维码的结果
* **二维码识别需要使用原图 多换几个参数来识别。
create_data_code_2d_model ('QR Code', 'default_parameters', 'enhanced_recognition', DataCodeHandleQR)
set_data_code_2d_param (DataCodeHandleQR, 'default_parameters', 'enhanced_recognition')
set_data_code_2d_param (DataCodeHandleQR, 'timeout', 200)
set_data_code_2d_param (DataCodeHandleQR, 'polarity', 'dark_on_light')
* 码粒个数设置(有几种二维码设置这个参数非法)
* set_data_code_2d_param (DataCodeHandleQR, 'symbol_size_max', 11)
* set_data_code_2d_param (DataCodeHandleQR, 'symbol_size_max', 31)
* 增强图像对比度(后文有该函数说明)
* enhanced_contrast (ImagePart, Image2, 40, 180)
Max := 254
Min := 1
Mult := 255.0 / (Max - Min)
Add := -Mult * Min
scale_image (tdcode, Image2, Mult, Add)
* 码粒像素设置
set_data_code_2d_param (DataCodeHandleQR, ['module_size_min','module_size_max'], [1,40])
* 如果GenParamNames, GenParamValues不填,那么默认只找一个二维码,将'stop_after_result_num'设置为3指最多找3个
find_data_code_2d (Image2, SymbolXLDs, DataCodeHandleQR, [], [], ResultHandles, DecodedDataStrings)
find_data_code_2d (Image2, SymbolXLDs, DataCodeHandleQR, 'stop_after_result_num', 3, ResultHandles, DecodedDataStrings)
tuple_length (DecodedDataStrings, Length)
* 找不到的话,改一下参数再找一遍
if (Length == 0)
set_data_code_2d_param (DataCodeHandleQR, ['module_size_min','module_size_max'], [1,40])
find_data_code_2d (Image2, SymbolXLDs, DataCodeHandleQR, [], [], ResultHandles, DecodedDataStrings)
endif
tuple_length (DecodedDataStrings, Length)
if (Length=0)
Max := 180
Min := 80
Mult := 255.0 / (Max - Min)
Add := -Mult * Min
scale_image (tdcode, Image2, Mult, Add)
* 码粒像素设置
set_data_code_2d_param (DataCodeHandleQR, ['module_size_min','module_size_max'], [1,40])
* 如果GenParamNames, GenParamValues不填,那么默认只找一个二维码,将'stop_after_result_num'设置为3指最多找3个
find_data_code_2d (Image2, SymbolXLDs, DataCodeHandleQR, [], [], ResultHandles, DecodedDataStrings)
find_data_code_2d (Image2, SymbolXLDs, DataCodeHandleQR, 'stop_after_result_num', 3, ResultHandles, DecodedDataStrings)
tuple_length (DecodedDataStrings, Length)
* 找不到的话,改一下参数再找一遍
if (Length == 0)
set_data_code_2d_param (DataCodeHandleQR, ['module_size_min','module_size_max'], [1,40])
find_data_code_2d (Image2, SymbolXLDs, DataCodeHandleQR, [], [], ResultHandles, DecodedDataStrings)
endif
tuple_length (DecodedDataStrings, Length)
if (Length == 0)
DecodedDataStrings := ''
endif
endif
return ()
从代码不难看出,程序是通过调用action()
来得到二维码识别结果的,根据热心网友提醒可能发生的ABI错误,函数声明时要添加 extern 关键字,同时为了测试方便,文件名采用绝对路径的形式:
///////////////////////////////////////////////////////////////////////////////
// File generated by HDevelop for HALCON/C++ Version 20.11.1.2
// Non-ASCII strings in this file are encoded in UTF-8.
//
// Please note that non-ASCII characters in string constants are exported
// as octal codes in order to guarantee that the strings are correctly
// created on all systems, independent on any compiler settings.
//
// Source files with different encoding should not be mixed in one project.
///////////////////////////////////////////////////////////////////////////////
#ifndef __APPLE__
# include "HalconCpp.h"
# include "HDevThread.h"
#else
# ifndef HC_LARGE_IMAGES
# include <HALCONCpp/HalconCpp.h>
# include <HALCONCpp/HDevThread.h>
# include <HALCON/HpThread.h>
# else
# include <HALCONCppxl/HalconCpp.h>
# include <HALCONCppxl/HDevThread.h>
# include <HALCONxl/HpThread.h>
# endif
# include <stdio.h>
# include <CoreFoundation/CFRunLoop.h>
#endif
using namespace HalconCpp;
extern "C"
{
// Procedure declarations
// Local procedures
void action(HTuple hv_path, HTuple* hv_result);
void get_2dcode_result(HObject ho_tdcode, HTuple* hv_DecodedDataStrings);
}
// Procedures
// Local procedures
void action(HTuple hv_path, HTuple* hv_result)
{
// Local iconic variables
HObject ho_Image;
// Local control variables
HTuple hv_DecodedDataStrings;
ReadImage(&ho_Image, hv_path);
//*输入图像
//*输出二维码的结果
get_2dcode_result(ho_Image, &hv_DecodedDataStrings);
//zoom_image_factor (Image, ImageZoomed, 2, 2, 'constant')
//get_2dcode_result (ImageZoomed, DecodedDataStrings2)
//zoom_image_factor (Image, ImageZoomed, 2, 2, 'constant')
//write_image (ImageZoomed, 'jpg', 0, '19.jpg')
(*hv_result) = hv_DecodedDataStrings;
return;
}
void get_2dcode_result(HObject ho_tdcode, HTuple* hv_DecodedDataStrings)
{
// Local iconic variables
HObject ho_Image2, ho_SymbolXLDs;
// Local control variables
HTuple hv_DataCodeHandleQR, hv_Max, hv_Min, hv_Mult;
HTuple hv_Add, hv_ResultHandles, hv_Length;
//*输入二维码区域的图像变量Image
//*输出二维码的结果
//stop ()
//**二维码识别需要使用原图 多换几个参数来识别。
CreateDataCode2dModel("QR Code", "default_parameters", "enhanced_recognition",
&hv_DataCodeHandleQR);
SetDataCode2dParam(hv_DataCodeHandleQR, "default_parameters", "enhanced_recognition");
SetDataCode2dParam(hv_DataCodeHandleQR, "timeout", 200);
SetDataCode2dParam(hv_DataCodeHandleQR, "polarity", "dark_on_light");
//码粒个数设置(有几种二维码设置这个参数非法)
//set_data_code_2d_param (DataCodeHandleQR, 'symbol_size_max', 11)
//set_data_code_2d_param (DataCodeHandleQR, 'symbol_size_max', 31)
//增强图像对比度(后文有该函数说明)
//enhanced_contrast (ImagePart, Image2, 40, 180)
hv_Max = 254;
hv_Min = 1;
hv_Mult = 255.0 / (hv_Max - hv_Min);
hv_Add = (-hv_Mult) * hv_Min;
ScaleImage(ho_tdcode, &ho_Image2, hv_Mult, hv_Add);
//码粒像素设置
SetDataCode2dParam(hv_DataCodeHandleQR, (HTuple("module_size_min").Append("module_size_max")),
(HTuple(1).Append(40)));
//如果GenParamNames, GenParamValues不填,那么默认只找一个二维码,将'stop_after_result_num'设置为3指最多找3个
FindDataCode2d(ho_Image2, &ho_SymbolXLDs, hv_DataCodeHandleQR, HTuple(), HTuple(),
&hv_ResultHandles, &(*hv_DecodedDataStrings));
FindDataCode2d(ho_Image2, &ho_SymbolXLDs, hv_DataCodeHandleQR, "stop_after_result_num",
3, &hv_ResultHandles, &(*hv_DecodedDataStrings));
TupleLength((*hv_DecodedDataStrings), &hv_Length);
//找不到的话,改一下参数再找一遍
if (0 != (int(hv_Length == 0)))
{
SetDataCode2dParam(hv_DataCodeHandleQR, (HTuple("module_size_min").Append("module_size_max")),
(HTuple(1).Append(40)));
FindDataCode2d(ho_Image2, &ho_SymbolXLDs, hv_DataCodeHandleQR, HTuple(), HTuple(),
&hv_ResultHandles, &(*hv_DecodedDataStrings));
}
TupleLength((*hv_DecodedDataStrings), &hv_Length);
if (0 != (int(hv_Length == 0)))
{
hv_Max = 180;
hv_Min = 80;
hv_Mult = 255.0 / (hv_Max - hv_Min);
hv_Add = (-hv_Mult) * hv_Min;
ScaleImage(ho_tdcode, &ho_Image2, hv_Mult, hv_Add);
//码粒像素设置
SetDataCode2dParam(hv_DataCodeHandleQR, (HTuple("module_size_min").Append("module_size_max")),
(HTuple(1).Append(40)));
//如果GenParamNames, GenParamValues不填,那么默认只找一个二维码,将'stop_after_result_num'设置为3指最多找3个
FindDataCode2d(ho_Image2, &ho_SymbolXLDs, hv_DataCodeHandleQR, HTuple(), HTuple(),
&hv_ResultHandles, &(*hv_DecodedDataStrings));
FindDataCode2d(ho_Image2, &ho_SymbolXLDs, hv_DataCodeHandleQR, "stop_after_result_num",
3, &hv_ResultHandles, &(*hv_DecodedDataStrings));
TupleLength((*hv_DecodedDataStrings), &hv_Length);
//找不到的话,改一下参数再找一遍
if (0 != (int(hv_Length == 0)))
{
SetDataCode2dParam(hv_DataCodeHandleQR, (HTuple("module_size_min").Append("module_size_max")),
(HTuple(1).Append(40)));
FindDataCode2d(ho_Image2, &ho_SymbolXLDs, hv_DataCodeHandleQR, HTuple(), HTuple(),
&hv_ResultHandles, &(*hv_DecodedDataStrings));
}
TupleLength((*hv_DecodedDataStrings), &hv_Length);
if (0 != (int(hv_Length == 0)))
{
(*hv_DecodedDataStrings) = "";
}
}
return;
}
#ifndef NO_EXPORT_MAIN
// Main procedure
void action()
{
// Local control variables
HTuple hv_result;
action("/home/sknp/Documents/JProject/diaoso/src/123.png", &hv_result);
std::cout << hv_result << std::endl;
}
#ifndef NO_EXPORT_APP_MAIN
#ifdef __APPLE__
// On OS X systems, we must have a CFRunLoop running on the main thread in
// order for the HALCON graphics operators to work correctly, and run the
// action function in a separate thread. A CFRunLoopTimer is used to make sure
// the action function is not called before the CFRunLoop is running.
// Note that starting with macOS 10.12, the run loop may be stopped when a
// window is closed, so we need to put the call to CFRunLoopRun() into a loop
// of its own.
HTuple gStartMutex;
H_pthread_t gActionThread;
HBOOL gTerminate = FALSE;
static void timer_callback(CFRunLoopTimerRef timer, void* info)
{
UnlockMutex(gStartMutex);
}
static Herror apple_action(void** parameters)
{
// Wait until the timer has fired to start processing.
LockMutex(gStartMutex);
UnlockMutex(gStartMutex);
try
{
action();
}
catch (HException& exception)
{
fprintf(stderr, " Error #%u in %s: %s\n", exception.ErrorCode(),
exception.ProcName().TextA(),
exception.ErrorMessage().TextA());
}
// Tell the main thread to terminate itself.
LockMutex(gStartMutex);
gTerminate = TRUE;
UnlockMutex(gStartMutex);
CFRunLoopStop(CFRunLoopGetMain());
return H_MSG_OK;
}
static int apple_main(int argc, char* argv[])
{
Herror error;
CFRunLoopTimerRef Timer;
CFRunLoopTimerContext TimerContext = { 0, 0, 0, 0, 0 };
CreateMutex("type", "sleep", &gStartMutex);
LockMutex(gStartMutex);
error = HpThreadHandleAlloc(&gActionThread);
if (H_MSG_OK != error)
{
fprintf(stderr, "HpThreadHandleAlloc failed: %d\n", error);
exit(1);
}
error = HpThreadCreate(gActionThread, 0, apple_action);
if (H_MSG_OK != error)
{
fprintf(stderr, "HpThreadCreate failed: %d\n", error);
exit(1);
}
Timer = CFRunLoopTimerCreate(kCFAllocatorDefault,
CFAbsoluteTimeGetCurrent(), 0, 0, 0,
timer_callback, &TimerContext);
if (!Timer)
{
fprintf(stderr, "CFRunLoopTimerCreate failed\n");
exit(1);
}
CFRunLoopAddTimer(CFRunLoopGetCurrent(), Timer, kCFRunLoopCommonModes);
for (;;)
{
HBOOL terminate;
CFRunLoopRun();
LockMutex(gStartMutex);
terminate = gTerminate;
UnlockMutex(gStartMutex);
if (terminate)
break;
}
CFRunLoopRemoveTimer(CFRunLoopGetCurrent(), Timer, kCFRunLoopCommonModes);
CFRelease(Timer);
error = HpThreadHandleFree(gActionThread);
if (H_MSG_OK != error)
{
fprintf(stderr, "HpThreadHandleFree failed: %d\n", error);
exit(1);
}
ClearMutex(gStartMutex);
return 0;
}
#endif
int main(int argc, char* argv[])
{
int ret = 0;
try
{
#if defined(_WIN32)
SetSystem("use_window_thread", "true");
#endif
// Default settings used in HDevelop (can be omitted)
SetSystem("width", 512);
SetSystem("height", 512);
#ifndef __APPLE__
action();
std::getchar();
#else
ret = apple_main(argc, argv);
#endif
}
catch (HException& exception)
{
fprintf(stderr, " Error #%u in %s: %s\n", exception.ErrorCode(),
exception.ProcName().TextA(),
exception.ErrorMessage().TextA());
ret = 1;
}
return ret;
}
#endif
#endif
**P.S. halcon在linux与windows导出的语言文件完全一致,但是在不同平台编译的方式有所不同,后文细嗦; **
在windows下编译
详情在参考链接【1】中,完全按照参考教程即可;
使用Visual Studio编译项目:
- 项目 属性页
- VC++目录 --> 包含目录 :%halcon安装目录%/include
- VC++目录 --> 库目录 :%halcon安装目录%/lib/x64-win64
- 链接器 --> 附加依赖项 :手动输入 halconcpp.lib (cpp项目依赖的库文件)
在我编译的过程中,出现了非语法问题,来回倒腾了几次莫名其妙就好了,如果遇到相同问题不妨留言讨论;
在linux下编译
接下来是困扰我很长时间的问题,上面说到在windows下编译需要 include (一系列头文件)和 .lib 文件,但是在linux下显然没有lib文件可用;在找了大量资料后,我悟了,可以这样理解:头文件和库文件是一起的,头文件是声明库文件函数的文件,也就是说在linux下找到头文件和库文件就能编译,这里的库文件也就是SO库!
好的,思路已经有了,开始实操~
首先要在linux环境下安装halcon,参考链接【3】可以顺利的安装,我选择的版本是 halcon 20.11.1 ;
配置好后,终端运行:
echo $LD_LIBRARY_PATH
显示结果:/opt/halcon/lib/x64-linux
代表安装成功,记住这个路径,里面的文件有大用;
开始编译,依次输入两条命令:
clang++ -c qr.cpp -I include/ -I include/halconcpp/ -fPIC
clang++ -shared -fPIC qrcode.o -o libqrcode.so -lhalcon -lhalconcpp
简单说说参数,为了方便,我把 include 文件夹复制到qr.cpp的同目录,使用
-I
引入上述路径,也可以根据实际情况选择路径;
-l
命令需要输入SO文件的libxxxx.so的中间部分,这里用到了 libhalcon.so 和 libhalconcpp.so
还有段插曲啊,在我没安装halcon前,-l
默认会在 /usr/lib
中寻找;好,那我把SO文件复制到目录下再引入,但!是!源文件全名是 libhalcon.so.20.11.1
和 libhalconcpp.so.20.11.1
,带后缀的SO库不能被编译器找到(超坑!)于是使用软连接:
ln -s libhalcon.so.20.11.1 libhalcon.so
ln -s libhalconcpp.so.20.11.1 libhalconcpp.so
个人建议安装halcon,以防缺这缺那的;
再看看打包的 libqrcode.so
有没有正确引用:
sknp@ubuntu:~/Documents/JProject/diaoso/src$ ldd libqrcode.so
linux-vdso.so.1 (0x00007ffcba34d000)
libhalcon.so.20.11.1 => /opt/halcon/lib/x64-linux/libhalcon.so.20.11.1 (0x00007fd3ee12a000)
libhalconcpp.so.20.11.1 => /opt/halcon/lib/x64-linux/libhalconcpp.so.20.11.1 (0x00007fd3edb65000)
libstdc++.so.6 => /lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007fd3ed971000)
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007fd3ed822000)
libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007fd3ed807000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fd3ed615000)
libX11.so.6 => /lib/x86_64-linux-gnu/libX11.so.6 (0x00007fd3ed4d6000)
libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007fd3ed4b3000)
librt.so.1 => /lib/x86_64-linux-gnu/librt.so.1 (0x00007fd3ed4a8000)
libXext.so.6 => /lib/x86_64-linux-gnu/libXext.so.6 (0x00007fd3ed493000)
libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007fd3ed48d000)
/lib64/ld-linux-x86-64.so.2 (0x00007fd3f2693000)
libxcb.so.1 => /lib/x86_64-linux-gnu/libxcb.so.1 (0x00007fd3ed461000)
libXau.so.6 => /lib/x86_64-linux-gnu/libXau.so.6 (0x00007fd3ed45b000)
libXdmcp.so.6 => /lib/x86_64-linux-gnu/libXdmcp.so.6 (0x00007fd3ed453000)
libbsd.so.0 => /lib/x86_64-linux-gnu/libbsd.so.0 (0x00007fd3ed439000)
主要看这两条:
- libhalcon.so.20.11.1 => /opt/halcon/lib/x64-linux/libhalcon.so.20.11.1 (0x00007fd3ee12a000)
- libhalconcpp.so.20.11.1 => /opt/halcon/lib/x64-linux/libhalconcpp.so.20.11.1 (0x00007fd3edb65000)
OKK下一步;
JNA调用SO库
好家伙,从halcon联合C#到JAVA调SO,把C#、C++、JAVA折腾个遍;
调用方式参考链接【4】,JAVA项目的配置就不多说了,上代码:
import com.sun.jna.Library;
import com.sun.jna.Native;
public class Jtest {
// 继承Library,用于加载库文件
public interface Clibrary extends Library {
// 加载libhello.so链接库
Clibrary INSTANTCE = (Clibrary) Native.loadLibrary("qrcode",
Clibrary.class);
// 此方法为链接库中的方法
int main();
}
public static void main(String[] args) {
// 调用
Clibrary.INSTANTCE.main();
}
}
最后
踩坑史:
在编译的时候提示undefine symbol错误,不明所以,后来在windows平台编译后才发现缺少halconcpp文件(巨坑,一开始的方向便是错的);
若不是之前网友提醒加上 extern "C" ,恐怕会更费劲;
总的来说,花了一天时间解决了“JAVA工程师”几天都无法解决的问题,还是有点小成就感的(
经过了这一天,我捡起了许久未用的C/C++和JAVA,朝着全干工程师更近了一步😂
随后升职加薪、迎娶白富美,走上人生巅峰…