NDK/JNI demo ( 五 ) ORB_SLAM2在Android上的移植过程
Android平台搭建和NDK环境配置
NDK是集成的Android中调用C++代码的工具包,核心是JNI(Java Native Interface)技术,具体这里略过不表。只说说NDK开发的基本步骤:
1. 编写Java代码:在Java中定义一个类,比如说叫NDKHelper吧,里面定义几个java的方法,只需要声明,不需要实现,如下所示:
public class NDKHelper {//NDK示例方法1public static native void ndkOne(int a,long b);//NDK示例方法2public static native int ndkTwo(String a,String b);
native标识符表示该函数将会利用C++代码完成实现。
接下来在工程上右键,Android Tools–>Add native support,出现如下界面:
名字就是最后我们要生成的库的名字,随便填,可修改。点击确定就会给你的工程添加C++编译支持,菜单栏会多了个小锤子:
这个是用来编译C++的快捷键。在你的工程目录下会新建jni目录和obj目录,其中jni目录用来存放和C++代码有关的东西,obj则存放C++进行编译时产生的中间件,最后生成的library会写入到libs文件夹下。
在jni文件夹中生成了如下文件,一个.cpp,一个Android.mk,其中.cpp是自动生成的,是用来编写C++部分的,而Android.mk类似C++里面的CMakeList,用来指定需要编译的文件和编译生成的模块名,一个最简单的Android.mk文件如下所示:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := NDKTest
LOCAL_SRC_FILES := NDKTest.cpp
include $(BUILD_SHARED_LIBRARY)
LOCAL_PATH :=$(call my-dir)表示包含当前目录。
include $(CLEAR_VARS)表示清除全部非系统变量和部分系统变量;
LOCAL_MODULE := NDKTest 表示当前生成的模块名,最终会生成libNDKTest.so文件
LOCAL_SRC_FILES := NDKTest.cpp 表示当前需要编译的cpp文件;
include $(BUILD_SHARED_LIBRARY) 表示生成共享库,需要生成静态库请修改成BUILD_STATIC_LIBRARY。
其他基础命令:
LOCAL_C_INCLUDES:= 表示添加头文件进入编译环境
LOCAL_LDLIBS:= 表示添加系统静态库
LOCAL_SHARED_LIBRARIES:= 表示添加共享库
其他命令请自行查看API文档。
这里指定了进行编译时的各项条件,如果需要指定编译器版本和编译目标平台等信息,则需要在jni目录下新建Application.mk文件,基本语句如下:
APP_STL := gnustl_static
APP_CPPFLAGS := -frtti -fexceptions
NDK_TOOLCHAIN_VERSION := 4.8
APP_ABI :=armeabi-v7a
APP_STL :=表示使用stl库,APP_CPPFLAGS表示一些CPP编译参数,NDK_TOOLCHAIN_VERSION 表示NDK使用的编译器版本,APP_ABI表示编译的目标平台,可以指定多个平台,平台之间用空格隔开,或者指定all则为全平台编译(armeabi,armeabi-v7a,mips,x86)。其他命令请自行查看API。
接下来编写对应的C++文件。
打开eclipse,点击Project–>build Project(若build automatically已勾选则会自动编译)打开命令行,cd到你的工程文件夹下的bin–>classes文件夹下,输入如下命令:
javah com.example.ndktest.NDKHelper
回车,则在你的classes文件夹下会生成对应的头文件。这里com.example.ndktest是你的package名字,NDKHelper是你的NDK函数的类名。
生成的头文件如下:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_example_ndktest_NDKHelper */#ifndef _Included_com_example_ndktest_NDKHelper
#define _Included_com_example_ndktest_NDKHelper
#ifdef __cplusplus
extern "C" {
#endif
/** Class: com_example_ndktest_NDKHelper* Method: ndkOne* Signature: (IJ)V*/
JNIEXPORT void JNICALL Java_com_example_ndktest_NDKHelper_ndkOne(JNIEnv *, jclass, jint, jlong);/** Class: com_example_ndktest_NDKHelper* Method: ndkTwo* Signature: (Ljava/lang/String;Ljava/lang/String;)I*/
JNIEXPORT jint JNICALL Java_com_example_ndktest_NDKHelper_ndkTwo(JNIEnv *, jclass, jstring, jstring);#ifdef __cplusplus
}
#endif
#endif
其他不用管,我们关注中间的两个函数声明:
JNIEXPORT void JNICALL Java_com_example_ndktest_NDKHelper_ndkOne(JNIEnv *, jclass, jint, jlong);
- 1
这个函数就是NDKHelper类中ndkOne函数对应的C++版本,其中JNIEXPORT和JNICALL是固定字段,void是函数返回值,函数名由Java字段+包名+类名+函数名组成,参数则多了几个JNI的系统参数JNIEnv 和jclass,其他的就是NDKHelper类中的对应参数,ndk会对该函数进行解析和链接,实现java和C++的对接。
将生成的.h头文件复制到jni目录下,新建对应的cpp文件,将该头文件include进来并对对应函数进行实现,实现过程就视函数功能而定。
这些工作完成后需要修改你的Android.mk文件,将刚刚新建的cpp和h文件包括进来。
然后点击开始那个小锤子或者直接项目右键RunAs–>Android Application,则C++部分会开始编译,编译具体过程可以在Eclipse下方Console窗口看到(如果没有Console窗口则点击Window–>Show Views,选择Console确定即可)。
编译完成后会生成对应的库存放在libs目录下,则你可以开始在Java里面调用刚才定义的ndkOne和ndkTwo函数实现具体的功能。
NDK基础到此为止,更深入的学习可以下载Android官方给的ndk samples.
ORB_SLAM2的移植
不想知道移植过程的童鞋可以直接下载我的Github源码:https://github.com/FangGet/ORB_SLAM2_Android 直接按照步骤进行即可。
移植过程
先看目录:
分为ORB和ThirdParty,其中ThirdParty包括boost clapack DBow2 g2o eigen3。
clapack和eigen来自于一个github的开源库:https://github.com/simonlynen/android_libs 这里集成了一些经典的C++库的ndk版本,下载即可使用。g2o和DBoW2则来自于ORB_SLAM2原作者的github地址,Boost是自己编译的lib,这里只介绍clapack和opencv的库配置。
clapack配置
从前述的开源库中将clapack目录拷贝到Thirdparty的对应目录下,clapack中已经包含了对当前目录极其子目录的编译过程,我们在jni目录下的Android.mk文件中加入如下内容:
include $(CLEAR_VARS)
MAINDIR:= $(LOCAL_PATH)
include $(MAINDIR)/Thirdparty/clapack/Android.mk
LOCAL_PATH := $(MAINDIR)include $(CLEAR_VARS)
MAINDIR:= $(LOCAL_PATH)
LOCAL_MODULE:= lapack
LOCAL_SHORT_COMMANDS := true
LOCAL_STATIC_LIBRARIES := tmglib clapack blas f2cLOCAL_EXPORT_C_INCLUDES := $(LOCAL_C_INCLUDES)
LOCAL_PATH := $(MAINDIR)
include $(BUILD_SHARED_LIBRA
这里的基本命令之前都已经讲过了,只补充如下几点内容:
- LOCAL_SHORT_COMMANDS是为了防止Windows对g++编译命令长度的限制而设置的参数,该参数会拖慢整个编译过程,因此请谨慎使用;
- LOCAL_EXPORT_C_INCLUDES表示将当前库的头文件EXPORT给系统,让程序代码中能实现<>的调用过程,若不设置这一参数则在cpp文件中可能无法引用该库;
- LOCAL_STATIC_LIBRARIES := tmglib clapack blas f2c是引用lapack子目录中编译好的一些依赖模块
这里会编译出一个名为lapack的库工程,该工程就可以作为依赖项被ORB所引用。
OpenCV的编译
opencv4Android是opencv官网为了对Android的支持而推出的一个工具集,可以在opencv官网进行下载。其目录结构如下:
其中sdk为核心部分,opencv4Android包含两个版本,一个是opencv为java做的本地化sdk,另一个是opencv利用ndk编译C++版本得到的库工程。我们将opencv4android解压后放置到ORB_SLAM2项目的同级目录下,如下所示:
之后在jni目录下的Android.mk中需要引用到OpenCV的地方加入如下代码:
OPENCV_LIB_TYPE:=STATIC
ifeq ("$(wildcard $(OPENCV_MK_PATH))","")
#try to load OpenCV.mk from default install location
include E:/ORB_SLAM2/OpenCV-2.4.9-android-sdk/sdk/native/jni/OpenCV.mk
else
include $(OPENCV_MK_PATH)
endif
这里opencv.mk我给的是绝对地址,其实相对地址也是可以的。上面这段引用会将opencv进行编译并引入到当前的工作模块上来,这里就完成了opencv库的基本调用。如果为了方便还可以将opencv自身单独编译成一个库工程并开放给其他模块引用。
其他libraries的编译过程和上述工程大同小异,其主要步骤可以概括如下:
- 将当前库复制到jni的特定目录下;
- 在Android.mk中新建一个模块并对模块进行命名;
- LOCAL_C_INCLUDE引入库的头文件,LOCAL_SRC_FILES引入库的cpp文件;
- LOCAL_LDLIBS/LOCAL_SHARED_LIBRARIES/LOCAL_STATIC_LIBRARIES引入依赖库;
- LOCAL_C_FLAGS设置编译参数;
ORB_SLAM2的编译
这里我们将ORB_SLAM2的源文件也编译为一个library以供调用,其编译过程和上面雷同,需要注意的是,由于pangolin编译有问题,我拆了源文件的pangolin部分并注释了对应的部分代码,同时引入了opengl es 来进行map和pose的绘制。同时,为了完成特征检测图像的回调,我改变了System.cc中TrackMonocular的返回值,将其返回值改成了Mat。
当上述过程完成后,我们的C++编译工作就基本完成了,最后也是最重要的一步是为Java中定义的native方法做C++的实现,在JAVA中,我定义了如下native函数:
/*** jni中初始化SLAM系统* @param VOCPath* @param calibrationPath*/public static native void initSystemWithParameters(String VOCPath,String calibrationPath);/*** Dataset模式中ORB系统的start函数* @param curTimeStamp* @param data* @param w* @param h* @return*/public static native int[] startCurrentORB(double curTimeStamp,int[] data,int w,int h);/*** Camera模式中ORB系统的start* @param curTimeStamp* @param addr* @param w* @param h* @return*/public native static int[] startCurrentORBForCamera(double curTimeStamp,long addr,int w,int h);/*** Opengl es 的初始化*/public native static void glesInit(); /*** opengl es绘制更新*/public native static void glesRender(); /*** 防止opengl es窗口resize带来的影响* @param width* @param height*/public native static void glesResize(int width, int height);
其对应的C++代码为:
/** Class: orb_slam2_android_nativefunc_OrbNdkHelper* Method: initSystemWithParameters* Signature: (Ljava/lang/String;Ljava/lang/String;)V*/
JNIEXPORT void JNICALL Java_orb_slam2_android_nativefunc_OrbNdkHelper_initSystemWithParameters
(JNIEnv * env, jclass cls, jstring VOCPath, jstring calibrationPath) {const char *calChar = env->GetStringUTFChars(calibrationPath, JNI_FALSE);const char *vocChar = env->GetStringUTFChars(VOCPath, JNI_FALSE);// use your stringstd::string voc_string(vocChar);std::string cal_string(calChar);env->GetJavaVM(&jvm);jvm->AttachCurrentThread(&env, NULL);s=new ORB_SLAM2::System(voc_string,cal_string,ORB_SLAM2::System::MONOCULAR,true);env->ReleaseStringUTFChars(calibrationPath, calChar);env->ReleaseStringUTFChars(VOCPath, vocChar);init_end=true;
}/** Class: orb_slam2_android_nativefunc_OrbNdkHelper* Method: startCurrentORB* Signature: (DDD[I)[I*/
JNIEXPORT jintArray JNICALL Java_orb_slam2_android_nativefunc_OrbNdkHelper_startCurrentORB(JNIEnv * env, jclass cls, jdouble curTimeStamp, jintArray buf, jint w,jint h) {jint *cbuf;cbuf = env->GetIntArrayElements(buf, false);if (cbuf == NULL) {return 0;}int size = w * h;cv::Mat myimg(h, w, CV_8UC4, (unsigned char*) cbuf);cv::Mat ima = s->TrackMonocular(myimg, curTimeStamp);jintArray resultArray = env->NewIntArray(ima.rows * ima.cols);jint *resultPtr;resultPtr = env->GetIntArrayElements(resultArray, false);for (int i = 0; i < ima.rows; i++)for (int j = 0; j < ima.cols; j++) {int R = ima.at < Vec3b > (i, j)[0];int G = ima.at < Vec3b > (i, j)[1];int B = ima.at < Vec3b > (i, j)[2];resultPtr[i * ima.cols + j] = 0xff000000 + (R << 16) + (G << 8) + B;}env->ReleaseIntArrayElements(resultArray, resultPtr, 0);env->ReleaseIntArrayElements(buf, cbuf, 0);return resultArray;
}
/** Class: orb_slam2_android_nativefunc_OrbNdkHelper* Method: glesInit* Signature: ()V*/
JNIEXPORT void JNICALL Java_orb_slam2_android_nativefunc_OrbNdkHelper_glesInit
(JNIEnv *env, jclass cls) {// 启用阴影平滑glShadeModel(GL_SMOOTH);// 黑色背景glClearColor(1.0f, 1.0f, 1.0f, 0.0f);// 设置深度缓存glClearDepthf(1.0f);// 启用深度测试glEnable(GL_DEPTH_TEST);// 所作深度测试的类型glDepthFunc(GL_LEQUAL);// 告诉系统对透视进行修正glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
}/** Class: orb_slam2_android_nativefunc_OrbNdkHelper* Method: glesRender* Signature: ()V*/
JNIEXPORT void JNICALL Java_orb_slam2_android_nativefunc_OrbNdkHelper_glesRender
(JNIEnv * env, jclass cls) {glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);glMatrixMode (GL_MODELVIEW);glLoadIdentity ();if(init_end)s->drawGL();
}/** Class: orb_slam2_android_nativefunc_OrbNdkHelper* Method: glesResize* Signature: (II)V*/
JNIEXPORT void JNICALL Java_orb_slam2_android_nativefunc_OrbNdkHelper_glesResize
(JNIEnv *env, jclass cls, jint width, jint height) {//图形最终显示到屏幕的区域的位置、长和宽glViewport (0,0,width,height);//指定矩阵glMatrixMode (GL_PROJECTION);//将当前的矩阵设置为glMatrixMode指定的矩阵glLoadIdentity ();glOrthof(-2, 2, -2, 2, -2, 2);
}/** Class: orb_slam2_android_nativefunc_OrbNdkHelper* Method: readShaderFile* Signature: (Landroid/content/res/AssetManager;)V*/
JNIEXPORT jintArray JNICALL Java_orb_slam2_android_nativefunc_OrbNdkHelper_startCurrentORBForCamera
(JNIEnv *env, jclass cls,jdouble timestamp, jlong addr,jint w,jint h) {const cv::Mat *im = (cv::Mat *) addr;cv::Mat ima = s->TrackMonocular(*im, timestamp);jintArray resultArray = env->NewIntArray(ima.rows * ima.cols);jint *resultPtr;resultPtr = env->GetIntArrayElements(resultArray, false);for (int i = 0; i < ima.rows; i++)for (int j = 0; j < ima.cols; j++) {int R = ima.at < Vec3b > (i, j)[0];int G = ima.at < Vec3b > (i, j)[1];int B = ima.at < Vec3b > (i, j)[2];resultPtr[i * ima.cols + j] = 0xff000000 + (R << 16) + (G << 8) + B;}env->ReleaseIntArrayElements(resultArray, resultPtr, 0);return resultArray;
这里解释下Dataset和Camera模式下start方法的区别。其实就是图像参数传递的方式不一样。在DataSet模式中,我们是用ImageView显示图片,用Bitmap读取文件中的图片,而非基本类型的数据都是不能被jni接口所接受的因此我们需要利用Bitmap的getPixels方法将其转换成int[]型数据进行传递,在jni中int[]对应的数据类型为jintArray,我们可以在获取到数据后将jintArray转换成Mat进行后续处理;而在Camera模式中我们是利用opencv android sdk中的cvCameraView 来直接进行摄像头的调用和图像的显示。其onCameraFrame(CvCameraViewFrame inputFrame)中的inputfram可以通过rgba()方法转换成Mat类型数据,而Mat类型同样不被jni识别,因此需要利用Mat的getNativeObjAddr方法获取Mat数据的long型指针传递到jni中进行处理。关键代码如下: DataSet 的Java部分:
int w = tmp.getWidth(), h = tmp.getHeight();//其中tmp为bitmap
int[] pix = new int[w * h];
tmp.getPixels(pix, 0, w, 0, 0, w, h);
3
C++部分:
jint *cbuf;
cbuf = env->GetIntArrayElements(buf, false);
if (cbuf == NULL) {return 0;
}
int size = w * h;
cv::Mat myimg(h, w, CV_8UC4, (unsigned char*) cbuf);
Camera的Java部分:
Mat im=inputFrame.rgba();
synchronized (im) {addr=im.getNativeObjAddr();//addr为函数传递的图像参数
4
C++部分:
const cv::Mat *im = (cv::Mat *) addr;//addr为传入的图像参数
1
结尾
当上述步骤都完成后,我们会得到最终生成的sdk。Android部分的布局文件和对应activity文件在这里也略过不表。当得到最终生成的apk后,我们如果要测试Camera模式,需要先将opencv4Android中apk文件夹中对应类型的opencv manager安装到手机中并预先打开才能使用,否则会提示找不到opencv的支持库;若只需测试Dataset模式则无需上述步骤。
相关文章:

[Nancy On .Net Core Docker] 轻量级的web框架
.net core现在已经有了大的发展,虽然笔者现在已经从事python开发,但是一直在关注.net的发展,在逛博客园的时候,发现有大家都会提到Nancy这个框架,在简单的使用之后,发现竟然是如此的简单而优雅 public clas…

【算法】【ACM】深入理解Dijkstra算法(单源最短路径算法)
Dijkstra算法是用来求解从某个源点到其他各顶点的最短路径(单源最短路径)。 下面的Dijkstra算法的讲解都是基于这个有向图,在遇到其他问题可以类比。 算法的基本思想: 把图中的定点分成两组,第一组包括已确定最短路径…

智能POS常见问题整理
智能POS预警值为小于所设的数量,H5就会变为锁定状态 智能POS查看数据库方法: 商米D1:设置-存储设备和USB-内部存储设备-浏览-winboxcash tablet.db为智能POS数据库 Winbox文件夹内,为相应logcat文件,应用出现问题时&am…
解决安卓系统写入SD卡权限问题
1.需要用户手动赋予的权限( Dangerous Permissions) 所属权限组权限日历READ_CALENDAR日历WRITE_CALENDAR相机CAMERA联系人READ_CONTACTS联系人WRITE_CONTACTS联系人GET_ACCOUNTS位置ACCESS_FINE_LOCATION位置ACCESS_COARSE_LOCATION麦克风RECORD_AUDIO…

【ACM】【STL】stack应用
C Stacks(堆栈) C Stack(堆栈) 是一个容器类的改编,为程序员提供了堆栈的全部功能,——也就是说实现了一个先进后出(FILO)的数据结构。 操作比较和分配堆栈empty()堆栈为空则返回真…

CHUCK手把手带你搞定OPENSTACK
以下是原文链接:http://blog.oldboyedu.com/openstack/转载于:https://blog.51cto.com/bovin/1858198

JVM基础面试题及原理讲解
2019独角兽企业重金招聘Python工程师标准>>> 本文从 JVM 结构入手,介绍了 Java 内存管理、对象创建、常量池等基础知识,对面试中 JVM 相关的基础题目进行了讲解。 写在前面(常见面试题) 基本问题 介绍下 Java 内存区域…
SLAM前端 ---------特征提取之ORB(ORB与SIFT与SURF)
ORB 论文翻译: 一种特征匹配替代方法:对比SIFT或SURF 1.ORB特征简介 ORB是Oriented FAST and Rotated BRIEF(oFAST and rBRIEF)的简称,ORB的名字已经说明了其来源,其实ORB特征是采用FAST方法来检测提取特…

oracle 内存分配和调优 总结
一直都想总结一下oracle内存调整方面的知识,最近正好优化一个数据库内存参数,查找一些资料并且google很多下。现在记录下来,做下备份。 一、概述: oracle 的内存可以按照共享和私有的角度分为系统全局区…

【ACM】Doubly Linked List(STL list)
题目链接:https://vjudge.net/problem/Aizu-ALDS1_3_C 这一题一开始的时候想的是用vector,超时 #include <iostream> #include <stack> #include <cstdio> #include <cstring> #include <queue> #include <vector>…

IOS获取焦点页面上移问题
var u navigator.userAgent; var flag; var myFunction; var isIOS !!u.match(/(i1;( U;)? CPU.Mac OS X/); if (isIOS) { document.body.addEventListener(focusin, () > { //软键盘弹起事件flag true;clearTimeout(myFunction); }) document.body.addEventListener(f…
SLAM之特征匹配(二)————RANSAC--------翻译以及经典RANSAC以及其相关的改进的算法小结
本文翻译自维基百科,英文原文地址是:http://en.wikipedia.org/wiki/ransac RANSAC是“RANdom SAmple Consensus(随机抽样一致)”的缩写。它可以从一组包含“局外点”的观测数据集中,通过迭代方式估计数学模型的参数…

【ACM】树 小结
树是一种表达层级结构的数据结构,也是实现高效算法与数据结构的基础。 学习之前的基础:数组,循环处理,结构体,递归函数。 树:由结点(node)和连接结点的边(edge…

【cocos2d-js官方文档】九、cc.loader
概述 原来的cc.Loader被改造为一个单例cc.loader,采用了插件机制设计,让loader做更纯粹的事。 各种资源类型的loader可以在外部注册进来,而不是直接将所有的代码杂揉在cc.Loader中,更好的方便管理以及用户自定义loader的创建。 cc…

更换VC后DDC提示证书不可用
问题描述:客户环境由Windows VC更换成Linux VC后,DDC提示证书不可用问题原因:因为VC更换后,存储在DDC数据库HostingUnitServiceSchema.HypervisorConnectionSSLThumbprint表中证书指纹信息和新得VC证书指纹信息不匹配。解决方法&a…
尺度空间理论与图像金字塔
我们之所以决定使用卷积后的特征是因为图像具有一种“静态性”的属性。也就是说使用卷积就是为了提取显著特征,减少特征维数,减少计算量。 在对图像进行卷积操作后的只管现象:图像变得模糊了,可是我们依然能够分辨出是什么&#x…

【ACM】 multiset 的 一些应用
一、The kth great number 题目链接:https://vjudge.net/problem/HDU-4006 用set写超时 (在VJ里,用C显示Compilation Error,选择G,则是TLE) #include <iostream> #include <set> #include &…

apache2.2 做后端,增加真实ip到日志中
apache2.2使用mod_remoteip模块 一.安装 wget https://github.com/ttkzw/mod_remoteip-httpd22/raw/master/mod_remoteip.c/usr/local/apache/bin/apxs -i -c -n mod_remoteip.so mod_remoteip.c 二.添加Apache配置vi /usr/local/apache/conf/httpd.confInclude conf/extra/htt…

高可用方案系统架构
2019独角兽企业重金招聘Python工程师标准>>> 高可用方案 系统架构 转载于:https://my.oschina.net/qiongtaoli/blog/3007587
OpenCV3.2.0+VS2017在window10开发环境配置记录
本机环境:win10 64位 OpenCV3.2.0 Visual Studio 2017 最后结果,亲测可用OpenCV官方下载地址: http://opencv.org/releases.html#本人选择opencv3.2.0基于Windows平台。读者根据自己需要选择合适版本及平台下载。 选择window版本的opencv下载…

C++vector迭代器失效的问题
转载:http://blog.csdn.net/olanmomo/article/details/38420907 转载:http://blog.csdn.net/stpeace/article/details/46507451 转载:http://www.cnblogs.com/xkfz007/articles/2509433.html 转载:http://www.cnblogs.com/BeyondAnyTime/archive/2012/08/13/2636375.html 有这样…

【HDU】1251统计难题 (字典树:二维数组,结构体数组,链表,map)
使用二维数组或者结构体数组都可以,但是在计数的时候有一点点小区别 一、结构体数组 #include <cstdio> #include <cstring> #include <algorithm> #include <iostream> #include <string> typedef long long ll; using namespace…

Jmeter干货 不常用却极其有用的几个地方
1. Jmeter测试计划下Run Thread Groups consecutively 表示序列化执行测试计划下所有线程组中的各个请求 如下图配置,新建的测试计划中,不默认勾选此项, 而享用Jmeter做接口自动化测试的同学们,会发现一个问题是,可能多…
图像滤波总结(面试经验总结)
目录 图像平滑处理,6种滤波总结的综合示例 【盒式滤波、均值滤波、高斯滤波、中值滤波、双边滤波导向滤波】 1-图像滤波 2-代码演示 3-显示结果 4-程序说明 5 角点检测(Harris,Fast,surf) 图像平滑处理,6种滤波总结的综合示…

【小贴士】DEV 多行注释
多行注释 Ctrl / 取消多行注释 Ctrl ,

JSP+Servlet基础一
2019独角兽企业重金招聘Python工程师标准>>> JSP中的指令: 格式:<%指令的名称(page,taglib,include...) 属性属性值%> 指令中的page:用于整个页面,定义与页面相关的属性。page属性一共有13个。 1、常…

Chameleon跨端框架——壹个理想主义团队的开源作品
文章较长,信息量很大,请耐心阅读,必然有收获。下面正文开始~背景解决方案原理久经考验生产应用举例易用性好多态协议学习成本低渐进式接入业内对比后期规划理想主义历经近20个月打磨,滴滴跨端方案chameleon终于开源了github.com/d…

尺度空间理论与图像金字塔(二)
SIFT简介 整理一下方便阅读,作者写的东西摘自论文,在此感谢xiaowei等的贡献 DoG尺度空间构造(Scale-space extrema detection)http://blog.csdn.net/xiaowei_cqu/article/details/8067881关键点搜索与定位(Keypoint l…

仿桌面通知pnotify插件
在做网站的时候,alert弹出框是非常常见的情形。但是,有些情况下,弹框对用户来说是并不友好的。调研了几个其他的提示插件了,发现pnotify比较好用,可配置性也高。 使用示例: <!DOCTYPE html> <html…

【HDU】1305 Immediate Decodability(字典树:结构体数组,二维数组,链表/指针)
一、用的二维数组 #include <iostream> #include <cstring> #include <algorithm> using namespace std;const int maxn 100; int tr[maxn][2]; int mk[maxn]; int tot;void insert(string s) {int u 0;for(int i0;i<s.length();i){int x s[i]-0;if(tr…