Quantcast
Channel: Sam的技术Blog
Viewing all 158 articles
Browse latest View live

OpenCL知识汇总

$
0
0
作者: Sam (甄峰)  sam_code@hotmail.com

之前做OpenCV移植时,注意到OpenCV可以选择Cuda或者OpenCL. 知道他们是不同阵营对GPU支持库。但对他们的结构和组成,并不了解。当前需要做以下工作:
A. 判断现有平台是否支持OpenCV。
B. 现在总结资料学习之。



1. OpenCL程序构成:
OpenCL最终控制到GPU,所以一定会有Kernel层代码。从底到上来说:
A. Kernel GPU Driver。 (buildIn 或者Modules)
B. 应用层动态库和对应头文件。(libOpenCL.so)
C. 应用程序。 

各个部分的获取和产生:
A. Kernel GPU Driver.
首先查看芯片技术手册,确认GPU型号。到对应GPU厂商查看是否支持OpenCL Driver.
如:ARM Mali-G51
https://developer.arm.com/products/software/mali-drivers/midgard-kernel

Imagination PowerVR: Series 5XT,SGX544
等都支持OpenCL. 

下载对应Driver.并加入Kernel Source Tree编译。

但此类工作还是留给芯片提供商自己搞定比较合适。

B. 应用层动态库:
或者动态库的方式有以下几种:
1. 如果用户为OpenCL专业人士,且有对应GPU相关文档和优化手册(ARM, Imagination相对容易获取, 高通的需要授权用户才可以)。 
可以利用:
https://github.com/KhronosGroup?utf8=&q=opencl&type=&language=
的资源,自行连接GPU Driver. 写出 动态库。

2. 从GPU芯片厂商下载:
ARM:
https://developer.arm.com/products/software/mali-drivers/user-space
ARM和Imagination可能的名称分别为:
libmali.so 和libPVROCL.so。

3. 最安全的做法:
SOC厂商获取。



 

OpenCL知识汇总

$
0
0
作者: Sam (甄峰)  sam_code@hotmail.com

1. 如何判断Device是否支持OpenCL:
想要在设备上运行OpenCL程序,首先要判断此设备是否支持OpenCL,我们先探访如何一步步确认Device是否支持OpenCL.
A. 软件测试:
OpenCL-Z
这个软件会探测Device是否支持OpenCL。并告知Android Version,  Kernel Version, OpenCL Version. OpenCL库的目录和文件, OpenCL库 Release的符号。


B.  查看库文件:
对高通,ARM,PowerVR来说,OpenCL库各不相同。
Qualcomm Adreno:
/system/vendor/lib/libOpenCL.so or /system/lib/libOpenCL.so (older devices)
64bit系统,则在:/system/vendor/lib64/libOpenCL.so 

ARM Mali:
/system/vendor/lib/egl/libGLES_mali.so
or /system/lib/egl/libGLES_mali.so
64bit: /system/vendor/lib64/egl/libGLES_mali.so


PowerVR:
/system/vendor/lib/libPVROCL.so


可以把这些库pull到本地,使用objdump -T libxxxx.so | grep clGetDeviceInfo

若可以看到OpenCL 的符号,则表示包含了OpenCL库。

00460f5c g    DF .text 0000005c clGetDeviceInfo


C. 编程测试:
int main(int argc, char** argv)
{
int sum=0;
char buffer[1024];
int* in,*out;
 


num_block=N/NUM_THREAD;

in=(int*)malloc(sizeof(int)*N);
out=(int*)malloc(sizeof(int)*num_block);
for(int i=0;i
in[i]=1;
}





Init_OpenCL();
Context_cmd();
Create_Buffer(in);
Create_program();
Set_arg();
Execution();
CopyOutResult(out);

for(int i=0;i
sum+=out[i];
}
printf("\nThere is [%d] block.\n", sum);


clGetPlatformInfo(platform[0],CL_PLATFORM_NAME,sizeof(buffer),buffer,NULL);
printf("\nOpenCL Platform Name: [%s]\n", buffer);

memset(buffer, 0, 1024);
clGetDeviceInfo(devices[0],CL_DEVICE_NAME,sizeof(buffer),buffer,NULL);
printf("\nOpenCL Device Name: [%s]\n", buffer);

return 0;
}




 

 void Init_OpenCL()
 {
size_t nameLen1;
char platformName[1024];
 
err = clGetPlatformIDs(0, 0, &num_platform);
platform=(cl_platform_id*)malloc(sizeof(cl_platform_id)*num_platform);
err = clGetPlatformIDs(num_platform, platform, NULL);
 
err=clGetDeviceIDs(platform[0],CL_DEVICE_TYPE_GPU,0,NULL,&num_device);
devices=(cl_device_id*)malloc(sizeof(cl_device_id)*num_device);
err=clGetDeviceIDs(platform[0],CL_DEVICE_TYPE_GPU,num_device,devices,NULL);
 
 }
 
 void Context_cmd()
 {
context=clCreateContext(NULL,num_device,devices,NULL,NULL,&err);
cmdQueue=clCreateCommandQueue(context,devices[0],0,&err);
 }
 
 void Create_Buffer(int *data)
 {
 
buffer=clCreateBuffer(context,CL_MEM_READ_ONLY|CL_MEM_COPY_HOST_PTR,sizeof(int)*N,data,&err);
sum_buffer=clCreateBuffer(context,CL_MEM_WRITE_ONLY,sizeof(int)*num_block,0,&err);
 }
 
 void Create_program()
 {
program=clCreateProgramWithSource(context, LEN(src), src, NULL, NULL);
err=clBuildProgram(program,num_device,devices,NULL,NULL,NULL);
kernel = clCreateKernel(program, "redution", NULL);
 }
 
void Set_arg()
{
err=clSetKernelArg(kernel,0,sizeof(cl_mem),&buffer);
err=clSetKernelArg(kernel,1,sizeof(cl_mem),&sum_buffer);
err=clSetKernelArg(kernel,2,sizeof(int)*NUM_THREAD,NULL);
}
 
void Execution()
{
const size_t globalWorkSize[1]={N};
const size_t localWorkSize[1]={NUM_THREAD};
  err=clEnqueueNDRangeKernel(cmdQueue,kernel,1,NULL,globalWorkSize,localWorkSize,0,NULL,NULL);
  clFinish(cmdQueue);
}
 
void CopyOutResult(int*out)
{
err=clEnqueueReadBuffer(cmdQueue,sum_buffer,CL_TRUE,0,sizeof(int)*num_block,out,0,NULL,NULL);
}
 

如果运行正常,获取到Device Name:
则证明从Driver, 到library都是正常的,可以运行OpenCL。





 

OpenCV编译总结

$
0
0
作者: Sam (甄峰)   sam_code@hotmail.com

自从2011年开始交叉编译OpenCV, 到今天陆陆续续编译了很多版本, 有尝试过编译Linux版本(http://blog.sina.com.cn/s/blog_602f87700102wuv7.html),交叉编译过ARM版本,在Android版本出现后,又编译过OpenCV4Android (http://blog.sina.com.cn/s/blog_602f87700102wwvb.html)  (http://blog.sina.com.cn/s/blog_602f87700102xdzq.html).


编译这么多次后,希望对自己的编译历程做个总结,所以借这次机会记录下来。


0. 交叉编译OpenCV的原因
0.1:OpenCV2.0时代:
最初交叉编译OpenCV,是想在Android系统上使用它,而在OpenCV2.0时代,并不提供Android版本。所以下载了OpenCV2.0 Source For Linux. 对各个模块自己写Android.mk, Application.mk. 顺利的编译出对应版本。
cvaux,cv, cxcore, ml, highgui等全部成功编译。且highgui底层支持V4L2, libv4l, ffmpeg等
(http://blog.sina.com.cn/s/blog_602f87700101de4o.html)

0.2: OpenCV3.1时代
OpenCV3.1时代,已经开始提供OpenCV4Android,理论上,根本不需要再自行编译.
OpenCV的官方建议是: 直接使用OpenCV库。libopencv_java.so.
并提供了两套使用方法:
A. 利用OpenCV提供的全套Java接口, 在Android Java层使用。
B. 利用OpenCV提供的C/C++ 接口, 在JNI层使用。

但Sam当时需要比较特殊: 与官方提供过使用方法不同的是:需要在Android NativeC控制台层面使用OpenCV。这本身没有问题,但会遇到Camera问题(注1)。

换句话说,就是要在Android NativeC 层, 把V4L2支持也打开。这就是每升级一次OpenCV, Sam都需要重新编译的核心原因。

0.3: OpenCV3.4时代
从OpenCV3.3. 它加入了DNN,需要用它,还想把OpenCL加入。又需要用到Camera,所以.....


1. OpenCV编译思路:
既然知道了特殊的OpenCV使用要求(在NativeC控制台程序中使用OpenCV, 同时提供Camera接口)。 那编译思路就很清晰了。
1.1:打开所有想要支持的模块和功能:
如:TBB, Eigen, OpenCL.

1.2:  打开V4L2支持。
此处要注意,使用官方方法打开V4L2支持,并不一定可以起效。因为在设置文件中,这个设置会被去掉。

1.3:堵住Native-JNI所用的Camera接口:
这个接口是给JNI-NativeC用的,与OpenCV-Manager关联。不是Sam所能使用的。所以需要把这条路堵住。



2. 编译方法研究总结
回顾:
OpenCV2.0时代,当时编译工具还采用autoconf. 所以直接自己写Android.mk , Application.mk。
OpenCV2.4.20时代,编译工具切换到cmake. 所以编译方法有所变化。直到OpenCV3.4, (OpenCV4.0还没看)。 一直可以延续这种编译方法。(中间也许添加了Python编译方法,后续再研究)。

首先看这种编译方法:
2.1: 几个关键文件
A. CMakeLists.txt
在OpenCV Source Tree根目录。有CMakeLists.txt. 它是OpenCV CMake 的根 Makefile。
B: platforms/scripts 目录
V2.4.20,  V3.1.0  V3.3.0中,platforms/scripts目录中,有很多 sh 脚本。
这些脚本用来帮助开发人员编译不同平台,不同架构的OpenCV.
cmake_android_arm.sh  :  Android ARM平台版本。
cmake_android_mips.sh: Android MIPS平台版本
cmake_android_x86.sh:  X86版本。
cmake_arm_gnueabi_hardfp.sh:  ARM hardfp版本。

其中,我们用到的就是cmake_android_arm.sh

这些脚本,其实是帮助开发人员编译的。
它的主要思想是:
#cd platforms
#sh scripts/cmake_android_arm.sh

它就会在platforms下建立一个类似 build_android_arm的目录。并生成(新产生,而非copy)将要编译的源码和头文件以及Makefile。等待用户编译。


其中,不同平台,不同架构的的编译设置, -DCMAKE_TOOLCHAIN_FILE=../android/android.toolchain.cmake:放在platforms/中不同目录内。
如:android,  ios,  linux, osx等。
android-ARM的编译配置文件:就在android/android.toolchain.cmake 中 .

C: platforms/android
这个目录放置的是android-arm版本的编译设置选项。
其中,V3.4.1版本,在platforms/scripts中,已经没有cmake_android_arm.sh 了。
但在platforms/android/ 增加了build_sdk.py , ndk-xx.config.py等。 估计是为python编译准备的。
不过,既然platforms/android/android.toolchain.cmake  还在,那老版本应该还行的通。

 
3. 编译前的准备工作
不同OpenCV版本的编译中,要求各个软件和库的版本也在不断升级中。所以就不提具体版本了。只说需要什么软件吧。 
git 
cmake
Android NDK
JDK
Android SDK
Apache Ant
Python.

环境变量设置:

export ANDROID_NDK=/opt/android-ndk-r14b/
export ANDROID_SDK=/home/sam/Android/android-sdk-linux/
#export ANDROID_SDK=/home/sam/Android/Sdk/
export ANDROID_ABI=armeabi-v7a

export ANT_HOME=/usr/share/ant
export PATH=${PATH}:${ANT_HOME}/bin

Sam 把他们写到一个脚本中:
SET_ENV_32BIT

#source  SET_ENV_32BIT 

4.  实际编译:
4.1:设置环境变量:
#source  SET_ENV_32BIT 

4.2:
哪怕是V3.4.1, Sam也建议自己建立scripts/cmake_android_arm.sh. 这样在后续修改设置,如加入TBB,OpenCL等时,就直接添加在cmake_android_arm.sh 中了。


#cd platforms
#sh scripts/cmake_android_arm.sh

#cd build_android_arm
#make VERBOSE=1



5.  修改OpenCV设置
5.1: Eigen支持添加:
A. 添加设置:  
在scripts/cmake_android_arm.sh 中,添加:
-DHAVE_EIGEN=1
但此时可以看到:
--   Other third-party libraries:
--     Eigen:                       YES (ver ..)
说明没找到Eigen的库和头文件。
查看CMakeLists.txt, 可以看到以下语句:
if(WITH_EIGEN OR HAVE_EIGEN)
  status("    Eigen:"      HAVE_EIGEN       THEN "YES (ver ${EIGEN_WORLD_VERSION}.${EIGEN_MAJOR_VERSION}.${EIGEN_MINOR_VERSION})" ELSE NO)
endif()
说明没有获取到以上信息。

查看CMakeList.txt:
OCV_OPTION(WITH_EIGEN          "Include Eigen2/Eigen3 support"               (NOT CV_DISABLE_OPTIMIZATION)       IF (NOT WINRT AND NOT CMAKE_CROSSCOMPILING) )
可以看到,如果是交叉编译,则WITH_EIGEN不被设置。

所以如果需要加入EIGEN支持,首先要交叉编译EIGEN, 再去掉这个语句。这个后面再尝试。







5.2: 添加TBB支持:
添加设置:
-DWITH_TBB=1
它会自动下载TBB。



5.3: 增加动态库
-DBUILD_SHARED_LIBS=ON



5.4:增加V4L2支持:























注1:
OpenCV中本机Camera支持:
Android Camera的原生接口与平台相关。 OpenCV将依赖于平台的代码隔离到单独的库中,并提供了几个预构建的库。如: libnative_camera_r2.2.0.so  libnative_camera_r4.0.3.so  libnative_camera_r4.1.1.so等。

OpenCV的本机Camera在控制台应用程序中不起作用



注2:
WITH_EIGEN, HAVE_EIGNE, WITH_TBB, HAVE_TBB关系。



 

Bazel详解初探

$
0
0
作者: Sam (甄峰)  sam_code@hotmail.com

0. Bazel 简介:
Bazel是个构建工具,它是Google开发并开源的一套软件。 与Make, Maven和Gradle等构建工具类似。
Make使用Makefile, Maven使用xml, Gradle则使用Groovy来指出如何编译哪些源文件,资源文件到目标。
Bazel则使用人类可读的高级构建语言来构建任意规模的软件。

0.1:Bazel优点如下:
1. Bazel 使用抽象的,人类可读的语言来描述项目在高级语义级别的构建属性。比Makefile,xml可以构建更复杂的项目。(和Gradle如何,Sam还不敢说)
2. Bazel快速可靠。只重建必要的东西。
3. Bazel支持多平台。在Linux, Windows, MacOS上可以运行。 且在同一个项目中为多个平台构建二进制文件。
4. Bazel可扩展。未来可以支持更多的平台和语言。
5. Bazel支持C, Java,iOS, Go和各种其它语言平台。

0.2:Bazel 安装
在Fedora下:
 dnf copr enable vbatts/bazel
 dnf install bazel
在Ubuntu下:
https://github.com/bazelbuild/bazel/releases


1. Bazel几个概念:
A. 工作区(WORKSPACE):
WORKSPACE是Bazel一个概念,它实质上是一个目录。这个目录是bazel工作时的一个基准目录。
Bazel规定,项目源文件和Bazel构建出的目标文件,均放在此目录。

要把一个目录设置为WORKSPACE, 则必须在此目录创建一个新的空文件,名为WORKSPACE.

Bazel构建项目时,所有的输入项和依赖项,必须位于同一个工作区内。
每个工作目录内,可以有多个项目(目录)。
bazel 编译或者执行其它命令时,是在WORKSPACE中。



B. BUILD文件
BUILD文件的作用类似 Makefile之于Make, xml文件之于Maven,   .gradle文件之于Gradle.
BUILD文件中包含bazel的各种不同类型的指令,其中包含构建规则,它指出Bazel如何利用给定的输入,构建出指定的输出。如利用什么.cpp文件,构建出库或者可执行程序。

2. Bazel的使用:
编译:
bazel build  //src:target 

显示编译:
bazel build -s  //src:target 
bazel build -s //src:V4L2_Utils --verbose_failures

clean;
bazel clean


3. BUILD 规则:
BUILD文件和Makefile在Make中的地位相同。但它更复杂。
Sam以V4L2_Utils为例,说明其基本实用方法。也把它的复杂指出表现出来。以供下一步研究。

Sam创建一个V4L2_Utils目录。 这个目录中,包含src目录,其中包含v4l2_utils.cpp, v4l2_utils.h . 在各个平台上,在各个构建模型下(Linux-Makefile, Android-NDK),它会构成一个动态库---libV4L2_Utils.so. 
还有一个main.cpp,它用来使用和验证这个动态库。

为了使用bazel, Sam构建了工作目录。在V4L2_Utils目录内,创建了WORKSPACE文件。

V4L2_Utils
-----------
src ,  WORKSPACE

src
---------
main.cpp  V4L2_Utils.cpp,  V4L2_Utils.h  BUILD


BUILD内容如下:
cc_library(
    name = "V4L2_Utils",
    srcs = ["v4l2_util.cpp"],
    hdrs = ["v4l2_util.h"],
)

cc_binary(
    name = "Test_V4L2",
    srcs = ["main.cpp"],
    deps = [
        ":V4L2_Utils",
    ],
)
看起来很简单,其实内部不少玄机。

比如:
cc_library,直观认为,这就是创建一个动态或者静态库。
cc_binary直观认为,这就是创建一个可执行程序。

可实际不是这样:
$bazel build //src:Test_V4L2 
时, 我们认为既然它依赖于V4L2_Utils,那应该创建一个库,然后在编译可执行文件吧?
其实不是,它会把v4l2_utils.o 和main.o编译到一起,合并成一个可执行程序。

但如果:
$bazel build //src:V4L2_Utils
那就会生成静态库和动态库各一个。





 

python记录

$
0
0
作者: Sam (甄峰)  sam_code@hotmail.com

Python学习中一些记录。


http://www.runoob.com/python/python-tutorial.html

 

TensorFlow_Numpy_document

$
0
0


https://tensorflow.google.cn/api_docs/

 

Tensorflow方法记录

$
0
0
作者:Sam(甄峰)  sam_code@hotmail.com

助记文档,长期维护,杂乱。



 

numpy方法记录

$
0
0
作者: Sam (甄峰)  sam_code@hotmail.com

numpy助记。长期维护。杂乱。

 

Tensorflow中的shape和rank

$
0
0
作者: Sam (甄峰)   sam_code@hotmail.com

TensorFlow中的数据以张量(Tensor)形式出现,张量(Tensor)可以看成一个多维数组。而shape则代表张量的形状。rank则表示Tensor的维度

1. tf.shape()讲解:
tf.shape(
    input
,
    name
=None,
    out_type
=tf.dtypes.int32
)

这个函数获取参数一(input)这个张量的形状(shape),并以整形一维array的形式提供出来。

这个形状比较特别,需要仔细查看, 以下为例子:
import tensorflow as tf

print("SamInfo")

c1 = tf.constant(value=[[[2., 4., 5.,4.],[3.,5., 5.,4.],[3.,5., 5.,4.]], [[2., 4., 5.,4.],[3.,5., 5.,4.], [3.,5., 5.,4.]]], dtype=tf.float32)
c2 = tf.constant([[2], [2]] ) 
c3 = tf.constant([1,2,3,4])


with tf.Session() as sess:
    print (sess.run(tf.shape(c1)))
    print (sess.run(tf.shape(c2)))
    print(sess.run(tf.shape(c3)))


输出为:

SamInfo
[2 3 4]
[2 1]
[4]



解释如下:

c3 = tf.constant([1,2,3,4])

c3这个常量是:[1,2,3,4]组成。在[]最外层,有4个单元,则shape为[4]



c2 = tf.constant([[2], [2]] ) 

c2这个常亮,最外层[]内有两个单元,而每个单元内,又各自有一个单元。所以shape为[2 1]



c1 = tf.constant(value=[[[2., 4., 5.,4.],[3.,5., 5.,4.],[3.,5., 5.,4.]], [[2., 4., 5.,4.],[3.,5., 5.,4.], [3.,5., 5.,4.]]], dtype=tf.float32)


c3这个常亮,最外层[]内有两个单元,而每个单元内,又各自有3个单元。最内层单元内,则有4个单元。所以shape为[2 3 4]





2. shape的使用:

2.1. 直接输入1-D array.

import tensorflow as tf



v2 = tf.random_uniform([3,3,2], minval= -1, maxval=1)

init = tf.global_variables_initializer()

with tf.Session() as sess: sess.run(init) print("Tensor is:", sess.run(v2))

将出现3个单元,每个单元又3个单元,最小的单元包含2个数字。

Tensor is: [[[-0.84239459 -0.64019585]
  [-0.20693684  0.61758161]
  [-0.39858079 -0.55873823]]

 [[ 0.63348198  0.6409862 ]
  [ 0.59288216 -0.47363114]
  [-0.80493784  0.34760666]]

 [[-0.63808179 -0.62189484]
  [-0.97743845  0.87292027]
  [-0.83223104  0.27168941]]]



2.2. 直接仿照其它Tensor的shape.

v2 = tf.random_uniform([3,3,2], minval= -1, maxval=1)
v3 = tf.zeros(tf.shape(v2))



3. Tensor的rank:

Tensor类似一个多维数组,那它的维度,就有rank表示。



c1 = tf.constant(value=[[[2., 4., 5.,4.],[3.,5., 5.,4.],[3.,5., 5.,4.]], [[2., 4., 5.,4.],[3.,5., 5.,4.], [3.,5., 5.,4.]]], dtype=tf.float32)



print ("C1 is:",sess.run(tf.shape(c1)), "rank is:", sess.run(tf.rank(c1)))




C1 is: [2 3 4] rank is: 3
表明它是个3-D Tensor。







 

Tensorflow中的feed

$
0
0
作者: Sam(甄峰) sam_code@hotmail.com

Tensorflow中,对于暂时不能赋值的元素,可以使用占位符。顾名思义,就是先占住位置,等需要时再赋值。它利用tf.placeholder()占住位置。 利用feed赋值。



赋值方式是:feed_dict={v:xxxx}
feed_dict参数的作用:
1. 是替换Graph中的某个Tensor的值。

例1:


import tensorflow as tf

v1 = tf.Variable(tf.ones([2,3], dtype=tf.int32))
r1 = tf.placeholder(dtype=tf.int32, shape=[3,2])


v2 = tf.Variable(2., dtype=tf.float32)
v3 = tf.Variable(3., dtype=tf.float32)
c1 = tf.constant(5., dtype=tf.float32)

mul = tf.multiply(v2, v3)
state = tf.multiply(mul, c1)


init = tf.global_variables_initializer()

mult = tf.matmul(v1, r1)

with tf.Session() as sess:
    sess.run(init)
    #print(sess.run(v1))
    #print(sess.run(mult, feed_dict={r1:[[2,3],[4, 5],[5,6] ]}))
    print(sess.run(state))
    print(sess.run(state, feed_dict={v2:4}))



结果: 

30.0

60.0


2.feed_dict可以用来设置graph的输入值
此时,它不是一个tensor, 而是一个占位符。
例2:
import tensorflow as tf

#2x3的Tensor。 全1
v1 = tf.Variable(tf.ones([2,3], dtype=tf.int32))  

#3x2 的Tensor.等待输入。
r1 = tf.placeholder(dtype=tf.int32, shape=[3,2])

init = tf.global_variables_initializer()

#两者想乘,应该是个2x2的Tensor
mult = tf.matmul(v1, r1)

with tf.Session() as sess:
    sess.run(init)
    print(sess.run(v1))
    print(sess.run(mult, feed_dict={r1:[[2,3],[4, 5],[5,6] ]}))
#临时给出了r1的值。计算结果

结果是:

[[1 1 1]
 [1 1 1]]
[[11 14]
 [11 14]]













 

OpenBlas的介绍和NDK下交叉编译

$
0
0
作者: Sam(甄峰)  sam_code@hotmail.com

1. OpenBLAS简介:
OpenBLAS 是一个基于GotoBLAS2的,经过优化的BLAS库。
BLAS(Basic Linear Algebra Subprograms)是做什么的呢?它提供一系列的基于线性代数的,向量和矩阵计算的程序。

2. 编译介绍:
正常模式下,要编译OpenBLAS,需要提前准备以下工具和软件:
GNU Make
A C compiler, e.g. GCC or Clang
A Fortran compiler (optional, for LAPACK)
IBM MASS (optional, see below)

其中,fortran编译器是可选的,它为lapack所准备。

Android NDK并不包含Fortran编译器。所以通常编译不包含Lapack的OpenBLAS版本。


如果是使用ARM交叉编译器编译OpenBLAS。则因为很多编译器支持Fortran,所以可以编译带Lapack的版本。



3. NDK编译:
Sam 选用clang. 编译32bit版本。
创建文件---set_ENV:
内容如下:
   
   export NDK_BUNDLE_DIR=/opt/android-ndk-r15c

   export PATH=${NDK_BUNDLE_DIR}/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin:${NDK_BUNDLE_DIR}/toolchains/llvm/prebuilt/linux-x86_64/bin:$PATH

   export LDFLAGS="-L${NDK_BUNDLE_DIR}/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/lib/gcc/arm-linux-androideabi/4.9.x"

   export CLANG_FLAGS="-target arm-linux-androideabi -marm -mfpu=vfp -mfloat-abi=softfp --sysroot ${NDK_BUNDLE_DIR}/platforms/android-23/arch-arm -gcc-toolchain ${NDK_BUNDLE_DIR}/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/ -I${NDK_BUNDLE_DIR}/sources/cxx-stl/llvm-libc++/include/"



   # make TARGET=ARMV7 ONLY_CBLAS=1 AR=ar CC="clang ${CLANG_FLAGS}" HOSTCC=gcc ARM_SOFTFP_ABI=1 -j4



$source set_ENV
$make TARGET=ARMV7 ONLY_CBLAS=1 AR=ar CC="clang ${CLANG_FLAGS}" HOSTCC=gcc ARM_SOFTFP_ABI=1 -j4

则可以编译出来了。






 

C/C++参数

$
0
0
作者: Sam (甄峰)  sam_code@hotmail.com

Sam第一语言是C语言,之后常使用C++. 但对函数参数的理解,还停留在C语言的概念里。今天,就把这块明确一下。


1:C语言参数
1.1: 形参和实参
形式参数(形参): 在函数定义中的参数,在整个函数体内都可以使用,离开该函则不能使用。
实际参数(实参): 出现在主调函数中,进入被调用函数后,实际参数也不能使用。

作用:
形参和实参的功能是做数据传递。
发生函数调用时,主调函数把实参的值传送给被调函数的形参,从而实现主调函数向被调函数的数据传送。这就是值传送

1.2:形参和实参的特点
A. 形参变量只有在函数被调用时才分配内存空间。 调用结束时,即刻释放所分配的内存。(所以形参只在函数内部有效)
B. 实参可以是常量, 变量,表达式,函数等。 但无论是何种类型,在进行函数调用时,他们都必须是确定的值。
C. 实参与形参在数量,类型,顺序上应该严格一致。
D. 函数调用中,数据传递是单向的,即实参把值copy给刚刚建立的形参,形参并不会把值传递给实参。所以改变形参的值,实参不变。

1.3:参数的传递过程
函数在被调用时,系统为形参分配一块内存空间,并把实参的内容copy给它。在函数体内,使用形参这个变量(内存)。 退出函数时, 临时存储区域被释放。
函数内部所用到的内存空间,由run-time stack配置而来,当函数结束时,这块内存空间会被立刻释放。

(与此类似,函数的返回值也是类似处理,返回值是局部变量,只在函数内有效,但在函数退出前,把返回值得内容copy给外部,所以局部变量销毁后,外部变量可以继续使用)

1.4:C语言中,数组作为参数
有些文档把数组名作为参数单列一种情况,其实它并不特殊。遵循的原则还是一致的---值传递。下面我们具体分析一下。
数组作为函数参数,又分两种情况:
1.4.1. 数组元素作为函数实参。与普通实参并无区别。都是值传递。


1.4.2. 数组名作为函数参数
数组名是个地址,即为数组的首地址。
数组名做参数,与数组元素做参数,在程序写法上有不同。
A. 使用数组元素做实参,只需要数组类型和函数形参类型一致即可。因此,并不需要函数形参也是下标变量。对数组元素的处理也是按普通变量对待。
而使用数组名作为实参时,则要求函数形参和相对应的实参都必须是类型相同的数组

B.在普通变量或者下标变量作为实参时,形参变量和实参变量是两个不同的内存区域。函数调用之初,,系统会把实参copy给形参。
在使用数组名作为实参时,数组名为指针, 它的值代表一个内存地址,形参的值就是一个内存地址。他们俩指向的同一块内存区域(数组)。 所以,他们的下标元素其实是同一套。

例1:
float average(float val[10]);





int main(int argc, char** argv)
{
float age = 0.0f; 

float fSize[10] = {1.2f, 3.4f, 1.0f, 3.4f, 6.5f, 7.2f, 3.5f, 4.0f, 4.4f, 6.6f};



printf("\nOld Data is: \n");
for(int i = 0; i < 10; i++)
{
printf("\t %f.\n", fSize[i]);
}

age = average(fSize);
printf("\nAverage is: %f\n", age);

printf("\nNew Data is: \n");
for(int i = 0; i < 10; i++)
{
printf("\t %f.\n", fSize[i]);
}

return 0;
}

float average(float val[10])
{
int index = 0;
float sum = 0.0f;

for(index = 0; index < 10; index++)
{
sum = sum + val[index];
}

for(index = 0; index < 10; index++)
{
val[index] = (float)index;
}
return sum/10;
}

这里有两点需要注意:
1. 在被调函数内,修改了数组参数的下标变量,结果实参也改变了。原因上面讲过。
2. 实参是数组名时,形参必须也是个同类型数组。
float average(float val[10]);

思考:
一个数组,如何定位,其实只需要一个数组名(头指针)和一个长度即可。
如例1 这样float average(float val[10]);
就固定了参数长度,如果未来要修改参数长度,就需要被调函数修改参数和内容。 所以其实可以不指定长度
如 float average(float val[], int len)
这样,如果实参所代表的数组长度改变,被调函数就不需要做任何修改了。(当然被调函数中10要转为len)





 

Anaconda安装和使用

$
0
0
作者: Sam (甄峰)  sam_code@hotmail.com

Anaconda简介:
Anaconda是一个开源的Python包,环境管理器。其中包含了conda,python等多个科学包和依赖项。
它可以再同一台机器上安装不同版本的软件包及其依赖项,并能够在不同环境之间切换。


Anaconda作用

我们都知道,在使用python时,需要有python解释器和相关package(包)。
python解释器根据Python的版本,分python2,python3,之间无法兼容。
python包集和中包含自带的包和第三方包。 当一个python环境中,不包含某个包时,那么引用这个包的程序则不能在该python环境下运行。

所以,当需要同时安装配置python2, python3,以及安装多个python package,配置不同环境时。可以用到Anaconda.



Anaconda安装:
下载:
https://www.anaconda.com/downloads

运行安装,此时可以看到有各种相关库和文件被安装。

安装成功后,可以看到:
  • Anaconda Navigtor :用于管理工具包和环境的图形用户界面,后续涉及的众多管理命令也可以在 Navigator 中手工实现。
  • Jupyter notebook :基于web的交互式计算环境,可以编辑易于人们阅读的文档,用于展示数据分析的过程。
  • qtconsole :一个可执行 IPython 的仿终端图形界面程序,相比 Python Shell 界面,qtconsole 可以直接显示代码生成的图形,实现多行代码输入执行,以及内置许多有用的功能和函数。
  • spyder :一个使用Python语言、跨平台的、科学运算集成开发环境。


Python包的安装:
Anaconda提供了命令行和图形界面两种方式。 图形界面更简单。

运行Anaconda Navigtor:

点选   Environments: 在查找框中输入想要安装的package(module)如: tensorflow
它会列出相关的modules和它的依赖项(如tensorboard, protobuf等)。 直接安装。












 

C++Reference

OpenCVforpython一环境安装

$
0
0
作者: Sam (甄峰)  sam_code@hotmail.com

sudo apt-get install python-opencv

$python 
>>> import cv2 as cv
>>>print(cv.__version__)
2.4.9.1



 

Tensorflow基础学习常量

$
0
0
作者: Sam (甄峰)  sam_code@hotmail.com

在Tensorflow学习中(for python),   常量是很常见的。想要创建一个Tensorflow Constant. 在Python接口下,使用: 
tf.constant(
    value
,
    dtype
=None,
    shape
=None,
    name
='Const',
    verify_shape
=False
)

创建一个常量Tensor.  这个Tensor由参数dtype指定每个元素类型。
A. 参数value可以是一个常量值,也可以是一个List,其中List的元素类型为dtype.
B. 如果参数value是一个list,那么List的长度必须小于或等于参数shape隐含的元素数。在List长度小于指定元素数量的情况下,List中的最后一个元素将用来填充剩余的元素条目。
C. 参数shape是可选的。如果存在,则指定Tensor的尺寸。如果不存在,value 的 shape则被使用。
D. 如果参数dtype没有指定,则从给定的value推断类型。

例:
1. value为常量, shape未设定。
m6 = tf.constant(1)
print("m6:", sess.run(m6), "\t shape :", m6.shape)
结果:m6: 1 shape : ()
结果是一个常量, 常量没有shape.

2. value为常量, 并指定shape
m9 = tf.constant(value=1, shape=[2,2])
print("m9:", sess.run(m9), "\t shape :", m9.shape)
结果:m9: [[1 1] [1 1]] shape : (2, 2)
可以看到,Tensor的Shape以参数shape设定的为准。两行,两列。其中的元素值,使用了value指定的常量。

3. value为一维List,不指定shape.
m8 = tf.constant([1,2,3,4,5,6,7])
print("m8:", sess.run(m8), "\t shape :", m8.shape)
结果:m8: [1 2 3 4 5 6 7]    shape : (7,)
一维List,没有shape, 则value的shape做为Tensor shape.
所以shape:(7, )

4. value为一维List, 指定Shape:
m10 = tf.constant(value=[1,2,3,4,5,6,7], shape=(2,4))
print("m10:", sess.run(m10), "\t shape :", m10.shape)
结果:m10: [[1 2 3 4] [5 6 7 7]] shape : (2, 4)
可以看到,指定了shape后,Tensor的shape就被定了。 此时是2行4列。
依次填充List的元素到其中,不够的话,重复最后一个元素。

5. value为多维List。不指定Shape:
m7 = tf.constant([[1],[2],[3],[4],[5]])
print("m7:", sess.run(m7), "\t shape :", m7.shape)
结果:m7: [[1] [2] [3] [4] [5]] shape : (5, 1) 
没有shape, 则value的shape做为Tensor shape.

























 

Tensorflow基础学习Variable

$
0
0
作者: Sam (甄峰)   sam_code@hotmail.com

1. Variable基础:
1.1: Variable class:
Variable是Tensorflow中常用的class。 它在graph中,透过调用run()来维护状态。可以调用class Variable的构造函数创建一个实例,则这个variable被添加到graph中。

Variable() 构造函数需要一个initial value作为variable的初始值。这个初始值可以是任意type或者shape的Tensor。 在构建variable后, 它的type和shape被固定。而它的值,则可以通过调用assign()方法被改变。

如果想要改变shape, 则可以使用assign() op, 同时,它的参数要做如下设置:validate_shape=False.(例子里会用到)

1.2:variable与Tensor关系:
和Tensor一样,由Variable()创建出的variable在Graph中,可以作为其它op的输入。此外,所有为Tensor class重载的操作,均可以以应用于variable. 所以咱们可以在graph中增加节点(op). 这些节点对variable进行操作。(因为variable可以当做Tensor运算)

举例来说:
import tensorflow as tf

#创建了2个variable
w1 = tf.Variable([[1,2,3],[4,5,6]])
w2 = tf.Variable([[1,2],[1,2],[1,2]])

#在graph中,w1,w2两个variable可以做Tensor用。
y = tf.matmul(w1, w2)

#使用tf.assign()换shape的方法
z = tf.assign(w2,w1,validate_shape=False)

init = tf.global_variables_initializer()



with tf.Session() as sess:
    sess.run(init)
    print(sess.run(y))
    print(sess.run(z))
    print("w1 is:", sess.run(w1), "shape is:", w1.shape)
    print("w2 is:", sess.run(w2), "shape is:", w2.shape)



 
2. Variable的初始化
当我们launch graph时, 必须在运行使用到variable的Op之前,初始化此variable.
可以使用初始化op显式初始化variable.     sess.run(w1.initializer)
初始化动作将从文件中取值给variable,或者简单的调用assign() op来分派值给variable. 事实上,Variable初始化op只是调用assign() op 把变量初始值分派给variable本身。

除了显式初始化,更普遍的方式是: 
init = tf.global_variables_initializer()
with tf.Session() as sess:
    sess.run(init)
则把所有variable都初始化了。

如果要创建一个初始值取决于另一个variable的变量,则要使用:initialized_value(). 它会确保初始化顺序正确。


3. 
所有的variable会自动收集在graph中。Variable的构造函数会把新的变量添加到Graph 的 GraphKeys.GLOBAL_VARIABLES. 中取。
函数global_variables()函数则可以返回当前Graph中所有该集合的内容。

在构建机器学习模型时,通常要区分可训练模型参数变量和其它变量。
为了能够指定是否可以训练,Variable构造函数制定了trainable=, 如果为True,则加入到GraphKeys.TRAINABLE_VARIABLES 集合中。
函数trainable_variables()返回此集合内容。
各种Optimizer Class使用此集合作为要优化的变量默认列表。



4. 实际使用:






 

python中的序列容器List

$
0
0
作者: Sam (甄峰)   sam_code@hotmail.com

0. 基础知识:
Python中,所有东西都是个对象。对一块连续内存区域,Python提供了各种不同的对象来对应。
比如: List tuple, dict, set等。 但他们与C++中的数组又有很大的区别。
C++的数组,单纯就是一块内存区域。
List等序列容器,它维护的是一块逻辑上连续的区域,但同时又提供了属性和方法。可以方便用户对这块区域进行操作。
除了内置的类型,还有一些Python Package提供了拥有更多功能的类型,例如添加加锁功能等。

序列,每个元素都分配一个数字,标明元素的位置或者说索引。 第一个是0, 第二个是1,以此类推。
序列可以进行:索引,切片,加,乘,检查成员,获取序列长度,获取序列最大最小值。

1. List相关:
Python中有6个序列的内置类型。最常见的就是List(列表)和tuple(元组), 元组可以看成不可修改的List。
1.1:List特点:
  • 是Python基本数据类型。
  • List中每个元素分配一个数字,标明元素的位置或者index.
  • List的数据项(元素)不需要具有相同的类型。

1.2:List使用:
1.2.1: List创建:
创建一个List,只需要把用逗号分隔的不同的数据项(不要求同类型)使用方括号括起来即可。
list1 = [1, "Just do it", 12.4, [1,2,3] ]
list2 = [1,2,3,4]

print(list1)
print(list2)

结果:
[1, 'Just do it', 12.4, [1, 2, 3]] 
[1, 2, 3, 4]

1.2.2: List添加元素
list.append(obj): 在List末尾添加新的元素。
list.extend(seq) : 在List末尾一次性最佳另一个序列中的多个值(用心列表扩展原来的列表)
list.insert(index, obj): 将一个对象加入到List中指定位置。
当list.insert(index, obj)中的index超过实际长度时,加到最后一位



例:
list1 = [1, "Just do it", 12.4, [1,2,3] ]
list2 = [1,2,3,4]

print("This is old list1", list1)
#print(list2)


list1.append(81)
list1.extend([43, "Kernel"])
list1.insert(1, 12)


print("This is new list1:", list1)

结果:
This is old list1 [1, 'Just do it', 12.4, [1, 2, 3]] 
This is new list1: [1, 12, 'Just do it', 12.4, [1, 2, 3], 81, 43, 'Kernel']

1.2.3:删除元素
del  list[index]  删除List中某index项目
list.remove(obj) 删除指定值obj的第一个匹配项
这两个方法,都有可能出异常:
del list[index],当index out of range时,会报异常。
list.remove(obj) 当list中不存在obj这个元素时,会报异常。



例子:
#Create List
list1 = [1, "Just do it", 12.4, [1,2,3] ]
list2 = [1,2,3,4]

print("This is old list1", list1)
#print(list2)

#add 
list1.append(81)
list1.extend([43, "Kernel"])
list1.insert(121, "error")
list1.insert(21, 12)

print("This is new list1:", list1)
print("Length is:", len(list1))



#del
list1.remove('Just do it')
del list1[0]

print("After remove. list1 is:", list1)
print("Length is:", len(list1))

结果:
This is old list1 [1, 'Just do it', 12.4, [1, 2, 3]] 
This is new list1: [1, 'Just do it', 12.4, [1, 2, 3], 81, 43, 'Kernel', 'error', 12] 
Length is: 9 
After remove. list1 is: [12.4, [1, 2, 3], 81, 43, 'Kernel', 'error', 12] 
Length is: 7
可以看到,index0和指定的"Just do it" 被删除了。


1.2.4:根据index访问
index = 4
print("index %d" % index + " obj is:", list1[index])

Python支持反向索引:
print("Last object is:", list1[-1])


例:
#Create List
list1 = [1, "Just do it", 12.4, [1,2,3] ]
list2 = [1,2,3,4]

print("This is old list1", list1)
#print(list2)

#add 
list1.append(81)
list1.extend([43, "Kernel"])
list1.insert(121, "error")
list1.insert(21, 12)

print("This is new list1:", list1)
print("Length is:", len(list1))



#del
list1.remove('Just do it')
del list1[0]

print("After remove. list1 is:", list1)
print("Length is:", len(list1))


#index
index = 4
print("index %d" % index + " obj is:", list1[index])

print("Last object is:", list1[-1])

list1[-2] = 'sucess'

print(list1)

结果:
This is old list1 [1, 'Just do it', 12.4, [1, 2, 3]] 
This is new list1: [1, 'Just do it', 12.4, [1, 2, 3], 81, 43, 'Kernel', 'error', 12] 
Length is: 9 
After remove. list1 is: [12.4, [1, 2, 3], 81, 43, 'Kernel', 'error', 12] 
Length is: 7 
index 4 obj is: Kernel 
Last object is: 12 
[12.4, [1, 2, 3], 81, 43, 'Kernel', 'sucess', 12]
可以看到,index = -2的error修改为success.


1.2.5: List 容量:
len(list):
if not list2:

print("list1 have %d object" % len(list1))

list3 = []
if not list1:
    print("list empty")
if not list3:
    print("list3 empty")

显示:
list1 have 7 object 
list3 empty

1.2.6:遍历:
for i in list1:
    print(i)

1.2.7: 切片:

切片是List取值的一种方法。取值是顾头不顾尾。返回结果类型和切片对象类型一致。
使用方法为:
list[start:end:step] 
start不指定时,缺省为0
end不指定时,缺省为最后一个。
例:
list1 = [12.4, [1, 2, 3], 81, 43, 'Kernel', 'sucess', 12]
list4 = list1[1::2]  步长为2,从1开始,到最后
print(list4)

list5 = list1[0:3]  从1开始,到3为止,不包括3
print(list5)

结果:
[[1, 2, 3], 43, 'sucess'] 
[12.4, [1, 2, 3], 81]


1.2.8:最小值,最大值:
max(list)
min(list)

1.2.9: obj获取index, obj出现次数:
list.index(obj)
list.count(obj)


2. 多维List:
Python本身没有多维List的概念。所谓多维,其实就是List的元素也是List。这样嵌套而成。
在TensorFlow和numpy中用到的很多。

list_m = [[1,2,3],[4,5,6],[7,8,9]]
print(list_m[2][1])  #第二行,第1列。 应该输出8


Python列表脚本操作符
列表对 + 和 * 的操作符与字符串相似。+ 号用于组合列表,* 号用于重复列表。

如下所示:

Python 表达式                            结果 描述
len([1, 2, 3])                        3  长度
[1, 2, 3] + [4, 5, 6]         [1, 2, 3, 4, 5, 6] 组合
['Hi!'] * 4                           ['Hi!', 'Hi!', 'Hi!', 'Hi!']       重复
3 in [1, 2, 3]                        True       元素是否存在于列表中



 

TensorFlowLiteNDK编译

$
0
0
作者: Sam (甄峰)     sam_code@hotmail.com

0. Tensorflow Lite 简介:
Tensorflow Lite是一组工具,用来帮助开发者在Mobile, Embedded Linux和IoT设备中运行Tensorflow models. 它支持在设备中执行机器学习Inference(推理)。 具有低延迟和二进制文件小等优点。

Tensorflow Lite包含两个主要组件:
A.  Tensorflow Lite interpreter(解释器):
它在mobile, embedded Linux, microcontroller上运行指定的TF Lite格式的模型。
B.  Tensorflow Lite Converter(转换器):
它将Tensorflow model转换到高效的TF Lite高效模型形式,准备给interpreter使用。


机器学习的边缘计算:
Tensorflow Lite旨在简化机器学习应用于终端设备的过程。(终端设备处于网络边缘,所以叫边缘计算),在终端设备而非服务器上计算。有以下优点:
  • 低延迟
  • 隐私性更好
  • 不需要连接网路
  • 功耗低

工作流程:
1. 选定一个Tensorflow model.
2. 使用Tensorflow Lite converter 转换Tensorflow Model 到TF Lite格式。
3. 使用Tensorflow Lite interpreter运行TF Lite Model. (支持多语言)
4. 优化模型

技术限制:
  • TF Lite计划提供让移动设备高效inference(推理)任意TF Model的能力。 但当前TF Lite interpreter(解释器)只支持Tensorflow Op 的子集。
  • Tensorflow Lite目前不支持设备上训练。

Android App中使用TF Lite:
方法1: 使用JCenter托管的Tensorflow Lite AAR.
dependencies {
    implementation
'org.tensorflow:tensorflow-lite:0.0.0-nightly'
}
方法2: 使用本地的TensorFlow Lite AAR.
把AAR放到工程的libs目录内。

方法3: 使用本地的 Tensorflow lite 动态库。
把库放到工程的libs里面。


1. 编译Tensorflow Lite:
1.1:编译ARM64 TensorFlow Lite:

1.2:编译NDK 模式:






 

OAL_Tengine学习基础知识

$
0
0
作者: Sam (甄峰)  sam_code@hotmail.com

0. 基础知识:
Tengine 是由开放智能实验室(Open AI Lab)开发的一个精简的,高性能用于嵌入式(ARM)设备的深度学习推理框架。在ARM平台,通过HCL计算库插件,它比TFLite等推理平台有较大的速度,CPU占用量上的优势。

Tengine支持ARM Cortex-A CPU, ARM Mali-GPU。

1. Tengine的定位:
当前训练框架,推理框架众多,但对AIoT的需求满足的并不好。一些框架效率较低,依赖库众多,在嵌入式平台部署困难。 为了让算法公司专注于算法(而不是做底层适配优化,如对CPU,GPU,DSP,xPUs等平台的优化)。
OAL推出Tengine推理平台。






2. 产品的构成:


可以看到:
A. 硬件层面,它支持CPU ,GPU等。
B. 模型层面, 它支持Caffe, MXNet,Tensorflow等训练出的模型格式。(当前版本好像又变到需要转到Tengine模型格式了)
C. 支持Caffe , Tensorflow等框架API。




可以看到,Tengine架构在HCL, Arm compute, OpenBLAS 库之上。


HCL是由Open AI Lab研发的异构计算库,专门用来加速ARM平台的NN计算。









 
Viewing all 158 articles
Browse latest View live