我的开发板和我

开发板:主人,别折磨我了😣😣😣

我:闭嘴🤐,你也别折磨我了😡😡😡

我做了什么

我在开发板上基于腾讯ncnn框架实现了目标检测计算。

ncnn是腾讯的一个神经网络前向计算框架。

ncnn is a high-performance neural network inference computing framework optimized for mobile platforms. ncnn is deeply considerate about deployment and uses on mobile phones from the beginning of design. ncnn does not have third party dependencies. It is cross-platform, and runs faster than all known open source frameworks on mobile phone cpu. Developers can easily deploy deep learning algorithm models to the mobile platform by using efficient ncnn implementation, create intelligent APPs, and bring the artificial intelligence to your fingertips. ncnn is currently being used in many Tencent applications, such as QQ, Qzone, WeChat, Pitu and so on.

ncnn 是一个为手机端极致优化的高性能神经网络前向计算框架。ncnn 从设计之初深刻考虑手机端的部署和使用。无第三方依赖,跨平台,手机端 cpu 的速度快于目前所有已知的开源框架。基于 ncnn,开发者能够将深度学习算法轻松移植到手机端高效执行,开发出人工智能 APP,将 AI 带到你的指尖。ncnn 目前已在腾讯多款应用中使用,如 QQ,Qzone,微信,天天P图等。

https://github.com/Tencent/ncnn

为什么选择做这个

接触了不少单片机的应用案例,在大部分案例中,单片机在做“控制”类的工作,即:控制外围设备以完成指定任务。也有一小部分案例中,单片机在做“计算”类的工作,例如:我在STM32单片机上跑神经网络算法

前者更多地是专业电子工程师的工作,我作为软件开发工程师,对后者更感兴趣。

通常情况下,为了让物联网设备更智能,我们需要利用互联网和云计算。物联网设备负责采集并上传数据,这些数据通过互联网传递到云计算中心,云计算中心计算后再将计算结果通过互联网返回给物联网设备。

将机器学习引入单片机后,我们既能赋予物联网设备更高的智能,又能减少数据上传,从而更好地保护用户隐私。同时也减少对互联网的依赖和数据在互联网上传输带来的延迟。

谷歌的TensorFlow Lite就支持在单片机上运行机器学习模型。

我是如何做的

将开发板连接到电脑

我使用串口调试功能实现两者之间的连接。

这一步中使用到了下面的器件:

  • 开发板
  • 杜邦线
  • micro-usb转 TTL 模块
  • usb to micro-usb转接线
  • typec to usb转接器

我使用MobaXterm软件来进行串口调试。

image-20220525152924669

1653462634822

从ncnn的repo中下载ncnn.tar.gz,然后上传到开发板并对其进行解压

1
2
tar zxf ncnn.tar.gz 
ls ncnn ncnn.tar.gz

安装依赖包

1
2
sudo apt update 
sudo apt install -y build-essential git cmake libprotobuf-dev protobuf-compiler libopencv-dev

编译ncnn

1
2
3
4
mkdir build 
cd build
cmake -DCMAKE_TOOLCHAIN_FILE=../toolchains/aarch64-linux-gnu.toolchain.cmake -DNCNN_SIMPLEOCV=ON -DNCNN_BUILD_EXAMPLES=ON ..
make -j$(nproc)

编译完成后,项目的目录结构如下所示。其中,build目录下的内容为编译结果。后续步骤中会使用到build目录下的nanodet

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
tree -L 2
.
├── benchmark
│   ├── alexnet.param
│   ├── benchncnn.cpp
│   ├── blazeface.param
│   ├── CMakeLists.txt
│   ├── efficientnet_b0.param
│   ├── efficientnetv2_b0.param
│   ├── googlenet_int8.param
│   ├── googlenet.param
│   ├── mnasnet.param
│   ├── mobilenet_int8.param
│   ├── mobilenet.param
│   ├── mobilenet_ssd_int8.param
│   ├── mobilenet_ssd.param
│   ├── mobilenet_v2.param
│   ├── mobilenetv2_yolov3.param
│   ├── mobilenet_v3.param
│   ├── mobilenet_yolo.param
│   ├── nanodet_m.param
│   ├── proxylessnasnet.param
│   ├── README.md
│   ├── regnety_400m.param
│   ├── resnet18_int8.param
│   ├── resnet18.param
│   ├── resnet50_int8.param
│   ├── resnet50.param
│   ├── shufflenet.param
│   ├── shufflenet_v2.param
│   ├── squeezenet_int8.param
│   ├── squeezenet.param
│   ├── squeezenet_ssd_int8.param
│   ├── squeezenet_ssd.param
│   ├── vgg16_int8.param
│   ├── vgg16.param
│   ├── yolo-fastest-1.1.param
│   ├── yolo-fastestv2.param
│   └── yolov4-tiny.param
├── build
│   ├── benchmark
│   ├── CMakeCache.txt
│   ├── CMakeFiles
│   ├── cmake_install.cmake
│   ├── examples
│   ├── Makefile
│   └── src
├── build-android.cmd
├── build.sh
├── cmake
│   ├── ncnn_add_layer.cmake
│   ├── ncnn_add_shader.cmake
│   ├── ncnnConfig.cmake.in
│   ├── ncnn_generate_arm82_source.cmake
│   ├── ncnn_generate_avx512_source.cmake
│   ├── ncnn_generate_avx_source.cmake
│   ├── ncnn_generate_fma_source.cmake
│   ├── ncnn_generate_msa_source.cmake
│   ├── ncnn_generate_rvv_source.cmake
│   ├── ncnn_generate_shader_comp_header.cmake
│   ├── ncnn_generate_shader_spv_header.cmake
│   └── run_test.cmake
├── CMakeLists.txt
├── codeformat.sh
├── CONTRIBUTING.md
├── docs
│   ├── application-with-ncnn-inside.md
│   ├── benchmark
│   ├── developer-guide
│   ├── faq.md
│   ├── Home.md
│   ├── how-to-build
│   └── how-to-use-and-FAQ
├── examples
│   ├── CMakeLists.txt
│   ├── fasterrcnn.cpp
│   ├── mobilenetssd.cpp
│   ├── mobilenetv2ssdlite.cpp
│   ├── mobilenetv3ssdlite.cpp
│   ├── nanodet.cpp
│   ├── nanodetplus_pnnx.cpp
│   ├── p2pnet.cpp
│   ├── peleenetssd_seg.cpp
│   ├── retinaface.cpp
│   ├── rfcn.cpp
│   ├── rvm.cpp
│   ├── scrfd.cpp
│   ├── scrfd_crowdhuman.cpp
│   ├── shufflenetv2.cpp
│   ├── simplepose.cpp
│   ├── squeezencnn
│   ├── squeezenet_c_api.cpp
│   ├── squeezenet.cpp
│   ├── squeezenetssd.cpp
│   ├── squeezenet_v1.1.bin
│   ├── squeezenet_v1.1.caffemodel
│   ├── squeezenet_v1.1.param
│   ├── squeezenet_v1.1.param.bin
│   ├── squeezenet_v1.1.prototxt
│   ├── synset_words.txt
│   ├── yolact.cpp
│   ├── yolov2.cpp
│   ├── yolov3.cpp
│   ├── yolov4.cpp
│   ├── yolov5.cpp
│   ├── yolov5_pnnx.cpp
│   └── yolox.cpp
├── glslang
│   ├── Android.mk
│   ├── BUILD.bazel
│   ├── BUILD.gn
│   ├── build_info.h.tmpl
│   ├── build_info.py
│   ├── build_overrides
│   ├── CHANGES.md
│   ├── ChooseMSVCCRT.cmake
│   ├── CMakeLists.txt
│   ├── CODE_OF_CONDUCT.md
│   ├── _config.yml
│   ├── DEPS
│   ├── External
│   ├── glslang
│   ├── gtests
│   ├── hlsl
│   ├── known_good.json
│   ├── known_good_khr.json
│   ├── kokoro
│   ├── license-checker.cfg
│   ├── LICENSE.txt
│   ├── ndk_test
│   ├── OGLCompilersDLL
│   ├── parse_version.cmake
│   ├── README.md
│   ├── README-spirv-remap.txt
│   ├── SPIRV
│   ├── StandAlone
│   ├── standalone.gclient
│   ├── Test
│   ├── update_glslang_sources.py
│   └── WORKSPACE
├── images
│   ├── 128-ncnn.png
│   ├── 16-ncnn.png
│   ├── 256-ncnn.png
│   ├── 32-ncnn.png
│   └── 64-ncnn.png
├── Info.plist
├── LICENSE.txt
├── package.sh
├── pyproject.toml
├── python
│   ├── CMakeLists.txt
│   ├── examples
│   ├── ncnn
│   ├── pybind11
│   ├── README.md
│   ├── requirements.txt
│   ├── setup.py.i
│   ├── src
│   └── tests
├── README.md
├── setup.py
├── src
│   ├── allocator.cpp
│   ├── allocator.h
│   ├── benchmark.cpp
│   ├── benchmark.h
│   ├── blob.cpp
│   ├── blob.h
│   ├── c_api.cpp
│   ├── c_api.h
│   ├── CMakeLists.txt
│   ├── command.cpp
│   ├── command.h
│   ├── convert_ycbcr.comp
│   ├── cpu.cpp
│   ├── cpu.h
│   ├── datareader.cpp
│   ├── datareader.h
│   ├── gpu.cpp
│   ├── gpu.h
│   ├── layer
│   ├── layer.cpp
│   ├── layer_declaration.h.in
│   ├── layer.h
│   ├── layer_registry.h.in
│   ├── layer_shader_registry.h.in
│   ├── layer_shader_spv_data.h.in
│   ├── layer_shader_type_enum.h.in
│   ├── layer_shader_type.h
│   ├── layer_type_enum.h.in
│   ├── layer_type.h
│   ├── mat.cpp
│   ├── mat.h
│   ├── mat_pixel_affine.cpp
│   ├── mat_pixel_android.cpp
│   ├── mat_pixel.cpp
│   ├── mat_pixel_drawing.cpp
│   ├── mat_pixel_drawing_font.h
│   ├── mat_pixel_drawing_font.png
│   ├── mat_pixel_resize.cpp
│   ├── mat_pixel_rotate.cpp
│   ├── modelbin.cpp
│   ├── modelbin.h
│   ├── net.cpp
│   ├── net.h
│   ├── option.cpp
│   ├── option.h
│   ├── paramdict.cpp
│   ├── paramdict.h
│   ├── pipelinecache.cpp
│   ├── pipelinecache.h
│   ├── pipeline.cpp
│   ├── pipeline.h
│   ├── platform.h.in
│   ├── simpleocv.cpp
│   ├── simpleocv.h
│   ├── simpleomp.cpp
│   ├── simpleomp.h
│   ├── simplestl.cpp
│   ├── simplestl.h
│   ├── stb_image.h
│   ├── stb_image_write.h
│   └── vulkan_header_fix.h
├── tests
│   ├── CMakeLists.txt
│   ├── prng.h
│   ├── test_absval.cpp
│   ├── test_batchnorm.cpp
│   ├── test_bias.cpp
│   ├── test_binaryop.cpp
│   ├── test_c_api.cpp
│   ├── test_cast.cpp
│   ├── test_clip.cpp
│   ├── test_command.cpp
│   ├── test_concat.cpp
│   ├── test_convolution1d.cpp
│   ├── test_convolution3d.cpp
│   ├── test_convolution.cpp
│   ├── test_convolutiondepthwise1d.cpp
│   ├── test_convolutiondepthwise3d.cpp
│   ├── test_convolutiondepthwise.cpp
│   ├── test_cpu.cpp
│   ├── test_crop.cpp
│   ├── test_deconvolution1d.cpp
│   ├── test_deconvolution3d.cpp
│   ├── test_deconvolution.cpp
│   ├── test_deconvolutiondepthwise1d.cpp
│   ├── test_deconvolutiondepthwise3d.cpp
│   ├── test_deconvolutiondepthwise.cpp
│   ├── test_deepcopy.cpp
│   ├── test_dequantize.cpp
│   ├── test_dropout.cpp
│   ├── test_eltwise.cpp
│   ├── test_elu.cpp
│   ├── test_expanddims.cpp
│   ├── test_flatten.cpp
│   ├── test_gelu.cpp
│   ├── test_gemm.cpp
│   ├── test_groupnorm.cpp
│   ├── test_gru.cpp
│   ├── test_hardsigmoid.cpp
│   ├── test_hardswish.cpp
│   ├── test_innerproduct.cpp
│   ├── test_instancenorm.cpp
│   ├── test_interp.cpp
│   ├── test_layernorm.cpp
│   ├── test_lrn.cpp
│   ├── test_lstm.cpp
│   ├── test_matmul.cpp
│   ├── test_mat_pixel_affine.cpp
│   ├── test_mat_pixel.cpp
│   ├── test_mat_pixel_drawing.cpp
│   ├── test_mat_pixel_resize.cpp
│   ├── test_mat_pixel_rotate.cpp
│   ├── test_memorydata.cpp
│   ├── test_mish.cpp
│   ├── test_multiheadattention.cpp
│   ├── test_noop.cpp
│   ├── test_normalize.cpp
│   ├── test_packing.cpp
│   ├── test_padding.cpp
│   ├── test_permute.cpp
│   ├── test_pixelshuffle.cpp
│   ├── test_pooling1d.cpp
│   ├── test_pooling3d.cpp
│   ├── test_pooling.cpp
│   ├── test_prelu.cpp
│   ├── test_priorbox.cpp
│   ├── test_quantize.cpp
│   ├── test_reduction.cpp
│   ├── test_relu.cpp
│   ├── test_reorg.cpp
│   ├── test_requantize.cpp
│   ├── test_reshape.cpp
│   ├── test_rnn.cpp
│   ├── test_roialign.cpp
│   ├── test_roipooling.cpp
│   ├── test_scale.cpp
│   ├── test_selu.cpp
│   ├── test_shufflechannel.cpp
│   ├── test_sigmoid.cpp
│   ├── test_slice.cpp
│   ├── test_softmax.cpp
│   ├── test_softplus.cpp
│   ├── test_squeeze.cpp
│   ├── test_squeezenet.cpp
│   ├── test_swish.cpp
│   ├── test_tanh.cpp
│   ├── test_tile.cpp
│   ├── test_unaryop.cpp
│   ├── testutil.h
│   └── test_yolov3detectionoutput.cpp
├── toolchains
│   ├── aarch64-linux-gnu-c.toolchain.cmake
│   ├── aarch64-linux-gnu.toolchain.cmake
│   ├── arm-linux-gnueabi-c.toolchain.cmake
│   ├── arm-linux-gnueabihf.toolchain.cmake
│   ├── arm-linux-gnueabihf-vfpv3-d16.toolchain.cmake
│   ├── arm-linux-gnueabi.toolchain.cmake
│   ├── c906.toolchain.cmake
│   ├── c906-v222.toolchain.cmake
│   ├── c906-v223.toolchain.cmake
│   ├── himix100.toolchain.cmake
│   ├── himix200.toolchain.cmake
│   ├── hisiv300.toolchain.cmake
│   ├── hisiv500.toolchain.cmake
│   ├── hisiv600.toolchain.cmake
│   ├── host-c.clang.toolchain.cmake
│   ├── host-c.gcc.toolchain.cmake
│   ├── host.clang-m32.toolchain.cmake
│   ├── host.gcc-c++03.toolchain.cmake
│   ├── host.gcc-m32.toolchain.cmake
│   ├── host.gcc.toolchain.cmake
│   ├── iossimxc.toolchain.cmake
│   ├── iossimxc-x64.toolchain.cmake
│   ├── ios.toolchain.cmake
│   ├── iosxc-arm64.toolchain.cmake
│   ├── iosxc.toolchain.cmake
│   ├── jetson.toolchain.cmake
│   ├── loongarch64-linux-gnu.toolchain.cmake
│   ├── mips32r2-linux-gnu.toolchain.cmake
│   ├── mips64el-linux-gnuabi64.toolchain.cmake
│   ├── mipsel-linux-gnu.toolchain.cmake
│   ├── mipsisa32r6el-linux-gnu.toolchain.cmake
│   ├── mipsisa64r6el-linux-gnuabi64.toolchain.cmake
│   ├── mips-mti-linux-gnu.toolchain.cmake
│   ├── pi3.toolchain.cmake
│   ├── powerpc64le-linux-gnu.toolchain.cmake
│   ├── riscv32-unknown-elf.toolchain.cmake
│   ├── riscv64-linux-gnu.toolchain.cmake
│   ├── riscv64-unknown-elf.toolchain.cmake
│   ├── riscv64-unknown-linux-gnu.toolchain.cmake
│   └── v831.toolchain.cmake
└── tools
├── caffe
├── CMakeLists.txt
├── darknet
├── keras
├── mlir
├── modelwriter.h
├── mxnet
├── ncnn2mem.cpp
├── ncnnmerge.cpp
├── ncnnoptimize.cpp
├── onnx
├── plugin
├── pnnx
├── pytorch
├── quantize
└── tensorflow

49 directories, 320 files

1653462645166

使用NanoDet进行目标检测

NanoDet 是一个速度超快和轻量级的移动端 Anchor-free 目标检测模型。

NanoDet 的具体位置为ncnn/build/examples/nanodet

新建一个 nanodet_demo 的文件夹,并将编译生成的 nanodet 可执行程序复制到 nanodet_demo 文件夹中

1
2
mkdir nanodet_demo 
cp ncnn/build/examples/nanodet nanodet_demo/

nanodet模型文件nanodet_m.binnanodet_m.param)和用于检测的图片上传到 nanodet_demo 文件夹中。

使用 nanodet 进行目标检测:

1
./nanodet dog1.png

检测完成的结果保存在名为 image.png 中。

1653462634862

1653462634880

展望

本开发版的接口是比较丰富的,后续会考虑通过GPIO口连接各种传感器,实现对周边环境的感知。

本开发版具备无线通信模块,可以较为方便地和其他设备联动,后面有机会的话会利用这方面的优势去做一些创新性的设计。

据我了解的情况,现在嵌入式领域有两个趋势:

  1. 使用更高级的语言(Python、JavaScript等)进行嵌入式开发,充分利用高级语言面向对象等优秀特性简化开发工作,提升代码质量。
  2. 引入嵌入式操作系统来进行资源管理。

在这样的趋势下,软件开发工程师、算法工程师也能参与到嵌入式开发中了。我会多去了解这些趋势,并将自己过往所学应用到该产品的后续升级中。

总结

这次课程设计让我受益良多。因为我并非专业的电子工程师,所以对于电子领域更多只是有个宏观的了解,但借助这次课程设计,我对电路、IO接口、gcc编译工具链等有了更加深入的认识。

在具体操作过程中,经常搞得焦头烂额,根据网上的教程一步一步走但就是复现不出结果。但好在最终做出了一个能跑起来的版本。整个过程让我对下面这句话有了更加深刻的认识:想要让OS为你提供服务,你得先伺候好OS。

总之,本次课程设计中,我综合运用了嵌入式和过往所学知识(C/C++、计算机组成原理、计算机网络、Git等),做出了个能跑起来的版本,这个过程是辛苦的,但也是收获颇丰的。