感受野相关

最近在重读目标检测的论文,着重了解了下SSD和Faster RCNN算法,发现感受野在目标检测中有比较重要作用,当然在分类问题时,感受野在网络设计上也有很大的作用。所以记录下收集到的和感受野相关的知识点。

  1. 感受野是某一层能在输入图片上看到的区域,这个是理论感受野。
  2. 感受野外的区域不起作用。
  3. 感受野大的时候可以有更多的语义信息进来,可能就是所谓的context。
  4. 感受野有理论感受野和有效感受野之分,有效感受野实际上很小,并且服务高斯分布,因为处于中间位置的参数对生成的feature map起的作用更大。比如3x3卷积中,处于最中心的参数被使用到的次数做多,产生的影像也很多。
  5. 有效感受野在网络参数初始化的时候比较小,虽然训练次数的增加,有效感受野变大。
  6. 下采样和膨胀卷积会增加感受野的尺寸,比如feature map的某个方向尺寸减半,理论感受野变成原来的2倍。
  7. 卷积层数变深,理论感受野的尺寸也会增大。
  8. dropout不改变有效感受野的高斯分布
  9. skip connection会导致有效感受野变小。
  10. 理论感受野的尺寸可能会比输入图像的尺寸更大
  11. SSD中6个尺度的feature上面的prior box的大小是不一样的,应该是依据感受野大小进行的设计,大的feature上的prior box会小一些,可以检测小的目标。小的feature上大一些,检测大的目标,因为对应的感受野会更大一些。

评论和共享

这篇blog要讲的是Jaderberg在ECCV2014上发表的Deep Features for Text Spotting,也就是自然场景下的文本识别。

字符识别分为两个步骤:

  1. 字符定位
  2. 字符识别

和文档中的字符识别相比,自然场景下的文本识别,由于字符背景的多样和字体本身的多样,导致字符识别在以上两个步骤中都比较困难。这篇文章为我们提供了一个思路,通过深度学习卷积神经网络(CNN)分别做字符的定位和识别。

总结起来,我们从文章中可以抽中如下步骤:

1. 训练一个text/no-text的二分类器以判断输入的图片是否是字符。

输入图片的大小为24×24.整个网络为全卷积网络,即不包含pooling,也没有全连接层。这样做的目的都是为了从一张图片中定位到字符而服务,因为我们需要从一张照片中定位到字符,而不是有现成的算法可以直接定位字符。如果有全连接层,对于非24×24的图片而言,放入CNN会无法forward,因为用24×24训练的全连接层的参数无法用在非24×24的图片上。如果有pooling的话,也会导致最终卷积得到的saliency map和原图不好对准。

这个二分类器也很简单,直接上原图:


eccv_0

输入24×24的图片,经过48 + 48 = 96个9 × 9的卷积核做一次conv之后,用maxout从96个结果中选出48个,从而得到48个16×16的feature map。接着再来64+64 = 128个9×9的卷积核卷积后得到128个8 × 8的feature map,经过maxout后剩下64个。再然后512个8×8卷积核做卷积。之后的maxout的分组个数为4,所以最终有512/4=128个feature map,且大小为1×1。我们只关注text/no-text分类器。所以只需要输出为2个1×1的feature map即可。
经过这个步骤,我们获得了一个字符/非字符分类器。也就是说你随便放入一张24×24大小的图片,这个分类器都会判定这张图片是字符还是非字符。

2. 对于一个完整的应用,我们需要从一张图片中找到字符的位置。

熟悉sliding window的同学可以能就会想到了,在目标图中建立24×24的sliding window,将窗口中的图片放入步骤1中的分类器,再加上对目标图的scaling,就可以定位字符的位置了。这样确实可以做,不过sliding window太耗时间,对于一张比较大的图,耗时将导致这个程序没有实用性。而我在前文中说到,这个文章的CNN没有全连接和pooling,利用这个特点,我们直接将目标图放到text/no-text分类器中即可。分类器会对这张目标图做卷积,而卷积的过程,实际上是进行了sliding window的过程的。
假设我们的目标图是24×48的:

2.1 如果要做sliding window,则需要从图片的左向右依次取出窗口中的图片,总共取出了25张大小为24×24的图片。这25张图片放入分类器,我们可以判断出25个对应的位置的图片是否是字符。

2.2 如果直接放入分类器,经过计算,最终输出的feature map的大小为2×1×25。2表示的是上文我提到的”所以只需要输出为2个1×1的feature map即可“,而25则恰好表示了25个位置的feature value,如此以来我们就不需要主动的使用sliding window。特别地,一般blas库都对卷积做了很多的优化,计算起来也很快,比主动做sliding window性能好很多。

上文提到的”2”,其实就是2分类器的输出,熟悉CNN的同学知道softmax之后,我们可以得到属于某一个类的概率。所以对于最终的1×25的feature map(其实就是saliency map),我们选出属于字符的位置,并映射到heat map上(比如heat map上每个像素值表示目标图中对应位置是字符的概率,然后设定一个阈值,如大于0.75以上才认为当前位置有字符),就可以确定字符的大概位置。

文章中对heat map又做了些处理。分别对heat map的每一行,计算出两个字符中间的间隙的均值和方差(注意文章中的µ和σ以及3µ−0.5σ),然后在两个点直接连线,从而形成连通域。有了连通域之后,可以画出bounding box(比如先find countour,然后画出每个contour对应的外接矩形,opencv都可以做),利用NMS合并一些bounding box,利用宽高比等再晒去一些可能不是字符的区域。总之最终我们可以良好的扣出目标中的字符位置。注意扣出来的图,只代表一行字符,而不是多行。比如下图:


eccv_0

这样我们才好做字符识别。

3. 这时候就可以从上面定位到的代表一行字符的图片中进行字符识别了。

这一步中需要用到字符分类网络,比如不区分大小写的话,有26+10=36个字符,如果我们再加入一个非字符的类别,那么就有了37类,这个分类其文章中也提到了。当我们有了37类的字符分类器后,我们将代表一行文字的图片直接扔到37分类器中,也相当与做了sliding window。对于每一个位置,我们都有一个概率来表示当前位置属于什么字符。这面就和上文提到的类似了。上面的图中,可以看到SHOGUN在对应字符的位置上概率比较大,用红色的圈表示。根据文章所属,蓝色区域从上到下分别表示no-text,0-9,a-z,共37个类别。当然文章中还用了bigram,我没有太关注,感觉不太需要bigram。

经过上述三个步骤我们就可以即定位,又识别字符了。
个人感觉对于字符大小变化多的场景下可能不太好用。因为我们需要对目标图做scaling,然后综合所有scaling的图片,确定字符的位置。如果字符有大有小,一些在某一个scale有可以定位的字符,在其他scale下估计就定位不到了,最终综合的时候,就比较诡异了…

评论和共享

总流程图:

1
2
3
4
5
6
7
8
9
10
stream_component_open() ->
ffpipeline_open_video_decoder() ->
func_open_video_decoder() ->
ffpipenode_create_video_decoder_from_android_mediacodec() ->
reconfigure_codec_l() \----> SDL_AMediaFormatJava_createVideoFormat()
\----> create_codec_l() -> SDL_AMediaCodecJava_createByCodecName() ->
\
\----> SDL_AMediaCodec_configure_surface() ->
func_configure_surface()[SDL_AMediaCodecJava_configure_surface()] ->
jmid_configure()[configure(MediaFormat format, Surface surface, MediaCrypto crypto, int flags) java]

  1. ff_ffplay.c 中stream_component_open()打开video stream.

  2. 调用
    ffpipeline_open_video_decoder()

  3. 调用
    func_open_video_decoder()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    static IJKFF_Pipenode *func_open_video_decoder(IJKFF_Pipeline *pipeline, FFPlayer *ffp)
    {
    IJKFF_Pipeline_Opaque *opaque = pipeline->opaque;
    IJKFF_Pipenode *node = NULL;

    if (opaque->mediacodec_enabled) //mediacodec_enabled可以在Java代码中设置以开启硬件解码. 由此处进入libstagefright的ACodec
    node = ffpipenode_create_video_decoder_from_android_mediacodec(ffp, pipeline, opaque->weak_vout);
    if (!node) //如果失败则进入ffmpeg的软件解码
    node = ffpipenode_create_video_decoder_from_ffplay(ffp);

    return node;
    }
  4. 首先通过SDL_AMediaFormatJava_createVideoFormat()调用 MediaFormat.java中的createVideoFormat()来配置各种metadata.然后在create_codec_l()中通过
    SDL_AMediaCodecJava_createByCodecName()或者SDL_AMediaCodecJava_createDecoderByType()调用MediaCodec.java的createByCodecName()或者createDecoderByType().
    Media

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
SDL_AMediaFormat *SDL_AMediaFormatJava_createVideoFormat(JNIEnv *env, const char *mime, int width, int height)
{
SDLTRACE("%s", __func__);
jstring jmime = (*env)->NewStringUTF(env, mime);
if (SDL_JNI_CatchException(env) || !jmime) {
return NULL;
}
//调用MediaFormat.java的createVideoFormat(),方便以后给MediaCodec.cpp传递各种在message中的metadata.
jobject local_android_media_format = (*env)->CallStaticObjectMethod(env, g_clazz.clazz, g_clazz.jmid_createVideoFormat, jmime, width, height);
SDL_JNI_DeleteLocalRefP(env, &jmime);
if (SDL_JNI_CatchException(env) || !local_android_media_format) {
return NULL;
}

jobject global_android_media_format = (*env)->NewGlobalRef(env, local_android_media_format);
SDL_JNI_DeleteLocalRefP(env, &local_android_media_format);
if (SDL_JNI_CatchException(env) || !global_android_media_format) {
return NULL;
}

SDL_AMediaFormat *aformat = SDL_AMediaFormat_CreateInternal(sizeof(SDL_AMediaFormat_Opaque));
if (!aformat) {
SDL_JNI_DeleteGlobalRefP(env, &global_android_media_format);
return NULL;
}

setup_aformat(aformat, global_android_media_format);
//这是一个设置mediaformat的使用例子.由于ACodec.cpp中有一个rotation-degrees描述视频旋转的.由于我们平时用手机竖屏拍摄的视频保存后其实是横向保存的,所以这类视频的rotation不为0.ijkplayer没有将rotation-degrees的值传给ACodec导致视频显示时是按照横向保存的方式显示的.
//这样我们可以使用SDL_AMediaFormat_setInt32(aformat, "rotation-degrees", 90);来纠正视频显示的方向.
SDL_AMediaFormat_setInt32(aformat, AMEDIAFORMAT_KEY_MAX_INPUT_SIZE, 0);
return aformat;
}

评论和共享

Compile WebRTC

Get and compile WebRTC with webrtc-build-scripts.

Follow the README.md of the repo to setup develop environment on ubuntu 14.04.
Notice that you may need VPN if you have no access to google.

1
2
3
4
5
6
$ source android/build.sh # setup several commands to pull and compile WebRTC code
$ install_dependencies # install some needed libs
$ install_dependencies # pull WebRTC source code

$ export WEBRTC_DEBUG=true # with this you compile for debug
$ build_apprtc # compile WebRTC and the example AppRTCDemo for all archs

To compile WebRTCDemo,

1
$ ninja -C /out/out_android_xx/Debug WebRTCDemo #xx stands for the arch of the target platform

To specify the arch(for example armv8), you may

1
$ export WEBRTC_ARCH=armv8

By default, it supports x86, x86_64, armv7 and armv8. To compile WebRTC for only one arch, do not use build_apprtc but

1
$ execute_build

Look into build.sh build_apprtc calls execute_build to compile for each arch.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
build_apprtc() {
export WEBRTC_ARCH=armv7
prepare_gyp_defines &&
execute_build

export WEBRTC_ARCH=armv8
prepare_gyp_defines &&
execute_build

export WEBRTC_ARCH=x86
prepare_gyp_defines &&
execute_build

export WEBRTC_ARCH=x86_64
prepare_gyp_defines &&
execute_build
}

After successfully compiling WebRTC you will find apks in out_android_xx.

AppRTCDemo’s source code locates under /android/webrtc/src/webrtc/example/androidapp/

WebRTCDemo’s source code locates under /android/webrtc/src/webrtc/example/android/

评论和共享

代码在github,下载后有Android纯Java代码,也有纯C++代码。

Java工程可以用IDEA打开编译然后安装apk。

C++代码需要先进入一个工程的jni目录,运行ndk-build编译出动态库。然后到有AndroidManifest.xml文件的目录下,用IDEA导入此工程,然后编译生成apk文件再安装到手机中。

使用模拟器的例子需要使用CMake+本地编译器编译,且需要安装PC端的OpenGL ES SDK。我安装了高通的Adreno SDK,但是用VS 2015编译成功后运行程序时崩溃,不知什么原因。Adreno号称支持OpenGL ES 3.0, 但是lib里只有2.0的。不知道是不是OpenGL ES版本不对造成的崩溃。

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ cd opengles3-book/Chapter_2/Hello_Triangle/Android/jni
$ ndk-build
[armeabi] Compile thumb : Hello_Triangle <= esShader.c
[armeabi] Compile thumb : Hello_Triangle <= esShapes.c
[armeabi] Compile thumb : Hello_Triangle <= esTransform.c
[armeabi] Compile thumb : Hello_Triangle <= esUtil.c
[armeabi] Compile thumb : Hello_Triangle <= esUtil_Android.c
[armeabi] Compile thumb : Hello_Triangle <= Hello_Triangle.c
[armeabi] Compile thumb : android_native_app_glue <= android_native_app_glue.c
[armeabi] StaticLibrary : libandroid_native_app_glue.a
[armeabi] SharedLibrary : libHello_Triangle.so
[armeabi] Install : libHello_Triangle.so => libs/armeabi/libHello_Triangle.so

$ cd ../
$ ls
-rw-rw-r-- 1 melody melody 615 10月 7 17:57 Android.iml
-rw-rw-r-- 1 melody melody 989 10月 7 17:45 AndroidManifest.xml
drwxrwxr-x 2 melody melody 4096 10月 7 17:55 jni

IDEA打开这个工程,然后编译后在out目录中生成
./out/production/Android/Android.apk
./out/production/Android/Android.unaligned.apk
安装apk文件到手机即可。
运行结果如下:
HelloTriangle

评论和共享

默认已经安装git for windows,由此可以使用git bash。所有操作均在git bash中进行。


安装node.js

node.js

安装后如果npm在git bash中无效则需要将npm添加到系统环境变量中。

安装hexo

在git bash中输入

1
2
$ npm install -g hexo-cli
$ npm install hexo --save

搭建博客

创建博客目录

1
2
3
$ cd where/you/like
$ mkdir my_local_blog
$ cd my_local_blog

创建hexo目录

1
$ hexo init

安装hexo插件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
npm install hexo-generator-index --save
npm install hexo-generator-archive --save
npm install hexo-generator-category --save
npm install hexo-generator-tag --save
npm install hexo-server --save
npm install hexo-deployer-git --save
npm install hexo-deployer-heroku --save
npm install hexo-deployer-rsync --save
npm install hexo-deployer-openshift --save
npm install hexo-renderer-marked@0.2 --save
npm install hexo-renderer-stylus@0.2 --save
npm install hexo-generator-feed@1 --save
npm install hexo-generator-sitemap@1 --save
npm install hexo-renderer-ejs --save

生成博客

1
$ hexo generate

或者

1
$ hexo g

查看本地博客效果

通过

1
$ hexo server

或者

1
$ hexo s

启动本地服务器,然后在浏览器的地址栏输入http://localhost:4000/,就可以测试博客了。

发布博客

首先配置_config.yml文件(此文件在hexo init之后会被覆盖掉,建议备份)。在此文件中加入

需要通过输入

1
$ ssh -T git@github.com

来配置ssh

1
2
3
4
5
6
# Deployment
## Docs: http://hexo.io/docs/deployment.html
deploy:
type: git
repository: git@github.com:username/username.github.com.git #<--这里使用ssh
branch: master

这样之后就可以通过

1
$ hexo deploy

或者

1
$ hexo d

将你的博客发布到github上了。此时你可以发现你的’my_local_blog’目录下的’.deploy_git’已经被push到github上了。以后每次执行’hexo deploy’都会将更新的博客push到github上。

评论和共享

  • 第 1 页 共 1 页
作者的图片

melody-rain

计算机视觉及深度学习


算法研究员


Shanghai, China