目录 ONVIF 协议 YiEDGE 边缘计算框架 管理 ONVIF 摄像头 同步到 Message Bus YiEVAS 系统 eKuiper 作为 pipeline 引擎 ONVIF 通告获取 stream rule-onvif-camera 拉流，抓帧，推理 拉流 抓帧 推理 结果输出 创建被管理摄像头 在边缘计算视频分析方面，ONVIF 协议实现了摄像头管理，YiEDGE 实现了拉流和图片输出，YiEVAS 模块 Pyairknn 实现推理。 ONVIF 协议 开放型网络视频接口论坛（英语：Open Network Video Interface Forum，简称ONVIF），是一个国际开放型网络视频产品标准网络接口开发论坛. IPC 摄像头必须支持 ONVIF 协议； 实现自动多摄像头并行处理性能要求； ONVIF Device Manager，用这个软件连接摄像头查看。 YiEDGE 边缘计算框架 YiEDGE 框架基于 EdgeX 框架做了深层次定制，实现了更加自动化的功能，增加了南向设备接口协议，增加了北向云平台接口协议。 管理 ONVIF 摄像头 采用 device onvif camera 设备层协议，对接各种支持 ONVIF 的摄像头设备； 定期探测网络中的 IPC； 轮训摄像头状态，获取 streamuri 地址；创建被管理摄像头 这里以 HK 摄像头为例，10s 轮训一次。 { "apiVersion": "v2", "statusCode": 200, "device": { "created": 1676646639846, "modified": 1676646710817, "id": "9a9a3276-cf19-4e01-908c-801a520ba5be", "name": "IPC-HK", "adminState": "UNLOCKED", "operatingState": "UP", "labels": [ "" ], "serviceName": "device-onvif-camera", "profileName": "onvif-camera", "autoEvents": [ { "interval": "10s", "onChange": false, "sourceName": "StreamUri" } ], "protocols": { "Onvif": { "Address": "192.168.123.183", "DeviceStatus": "UpWithAuth", "FirmwareVersion": "V5.7.3 build 220315", "FriendlyName": "HIKVISION DS-IPC-T12HV3-IA", "HardwareId": "88", "LastSeen": "Fri Feb 17 15:11:50 UTC 2023", "MACAddress": "c0:6d:ed:eb:cb:59", "Manufacturer": "HIKVISION", "Model": "DS-IPC-T12HV3-IA", "Port": "80", "SerialNumber": "DS-IPC-T12HV3-IA20220913AACHK56293791" } } } } streamUri 信息内容 [ { "id": "c88007b8-4316-451c-87b4-2dec7f1f540d", "origin": 1676646870752047000, "deviceName": "IPC-HK", "resourceName": "StreamUri", "profileName": "onvif-camera", "valueType": "Object", "value": "", "objectValue": { "MediaUri": { "InvalidAfterConnect": false, "InvalidAfterReboot": false, "Timeout": "PT60S", "Uri": "rtsp://192.168.123.183:554/Streaming/Channels/0?transportmode=unicast&profile=Profile_1" } } } ] 同步到 Message Bus 以上信息都会同步到数据总线； 可以自定义轮训时间； 筛选需要通告总线的 IPC； YiEVAS 系统 eKuiper 作为 pipeline 引擎 大概模式如下： |source输入->|rule规则引擎->|sink输出->| |:----|:----|:----| |onvif 通告获取|拉流，抓帧，推理|结果输出| ONVIF 通告获取 stream 这里，直接用 EdgeX 数据总线即可。 { "Name" : "EdgeXStream", "Options" : { "format" : "json", "type" : "edgex" }, "Statement" : null, "StreamFields" : null, "StreamType" : 0 } rule-onvif-camera 从 stream 获取信息，并解析 StreamUri 通告给拉流处理。 SELECT "true" as isRetImg, (json_path_query(StreamUri, "$")->MediaUri)->Uri AS rtspUrl, meta(deviceName) as deviceName FROM EdgeXStream where meta(profileName) = "onvif-camera" StreamUri 解析结果 { "deviceName": "IPC-HK", "isRetImg": "true", "rtspUrl": "rtsp://192.168.123.183:554/Streaming/Channels/0?transportmode=unicast&profile=Profile_1" } 拉流，抓帧，推理 拉流 通过 eKuiper video plugin：目前这个方式只能固定将 URI 写入配置文件，测试可以用，实际没有使用价值。 通过 YiEVAS application 实现：此处为商业代码和功能，有需要的可以咨询我们的热线： 18616669123 抓帧 可以通过图片帧，输出到 总线或 mqtt broker，进行按图片推理，也方便其他应用进行二次处理； 还可以直接拉流时对图片帧进行推理，效率很高，不经过总线的方式进行转发，前提是其他应用不需要视频流和图片； def get_frame(self): cap = cv2.VideoCapture(rtspUri) ret, frame = cap.read() if ret: image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) outputs = rknn_lite.inference(inputs=[image]) return outputs else: return None 推理 可以单独对图片进行推理，通过总线获取图片帧； 也可以在拉流过程中直接推理，具体看业务需要。 结果输出 此处结果与前面的系列文章结果类似，都是以输出到云平台为主。
技术｜在 RV1126 之上使用 rknn_toolkit_lite 和 eKuiper pipeline 进行图片识别推理
本文使用 Base64，OPENCV，Numpy 和 Image 组件进行数据读取转换。 图片格式 一般在边缘计算端进行推理，不会使用一张一张图片，而是采用视频流读帧的方式。 二进制文件格式 file_bytes = "binary file" // 初始化图片二进制 encoded = base64.decodebytes(file_bytes.encode("ascii")) // 转换成base64 img = Image.open(io.BytesIO(encoded)).resize((width, height)) // 打开图片 input_data = np.array(img) // 转换成 numpy image = cv2.cvtColor(input_data, cv2.COLOR_BGR2RGB) // OPENCV 读取图片 图片(二进制)-->base64-->Image-->numpy array-->opencv Base64 编码格式 如果是 Base64 格式输入，处理方法一样，但是，在输入流的地方，要注意 source 采用 json 编码。 // 代码通用 // 输入格式： {"image": "/9j/4AAQSk....X//2Q=="} 图片(json base64）-->Image-->numpy array-->opencv eKuiper source 读取源（输入输入） eKuiper 定义 source 唯一区别就是 format: binary/json，假设都是采用 mqtt broker 作为图片输入。 Binary 二进制格式 输入源定义： { "Name" : "binaryimage", "Options" : { "confKey" : "edgex_mqtt_broker", "datasource" : "image", "format" : "binary", "type" : "mqtt" }, "Statement" : null, "StreamFields" : null, "StreamType" : 0 } 输入图片示例代码, GOLANG，这里的 payload 直接是 ReadFile，二进制： package main import ( "fmt" "io/ioutil" "time" mqtt "github.com/eclipse/paho.mqtt.golang" ) func main() { const TOPIC = "binaryimage" images := []string{ "parrot.jpg", "owl.jpg", "out.jpg", // 其他你需要的图像 } opts := mqtt.NewClientOptions().AddBroker("tcp://192.168.123.184:1883") client := mqtt.NewClient(opts) if token := client.Connect(); token.Wait() && token.Error() != nil { panic(token.Error()) } for _, image := range images { fmt.Println("Publishing " + image) payload, err := ioutil.ReadFile(image) if err != nil { fmt.Println(err) continue } if token := client.Publish(TOPIC, 0, false, payload); token.Wait() && token.Error() != nil { fmt.Println(token.Error()) } else { fmt.Println("Published " + image) } time.Sleep(1 * time.Second) } client.Disconnect(0) } Json 格式 输入源定义： { "Name" : "jsonimage", "Options" : { "confKey" : "edgex_mqtt_broker", "datasource" : "image", "format" : "json", "type" : "mqtt" }, "Statement" : null, "StreamFields" : null, "StreamType" : 0 } 输入图片为 base64 编码后的结果，json 格式如下，发送到 mqtt broker 即可： image.json { "image" : "/9j/4AAQSkZJRgABAQEA8ADwAAD/7Ukit.....ucen55+KEpqqGC7pTdx3X//2Q==" } eKuiper rule 规则定义 labelImageRknn 函数是由 pyairknn 插件（portable）来实现的逻辑处理。 Json 输入格式处理 这里的 FROM jsonimage 来自上面定义的 source，其他根据自身需要修改即可。labelImageRknn(image) 为 MQTT broker 输出内容作为 rule 的输入，这个有些难以理解，多试几遍就明白了。 { "actions" : [ { "mqtt" : { "bufferLength" : 1024, "enableCache" : false, "format" : "json", "insecureSkipVerify" : false, "omitIfEmpty" : false, "protocolVersion" : "3.1.1", "qos" : 0, "resourceId" : "edgex_mqtt_broker", "runAsync" : false, "sendSingle" : true, "server" : "tcp://edgex-mqtt-broker:1883", "topic" : "label" } } ], "id" : "rule-mqtt-image", "options" : { "bufferLength" : 1024, "checkpointInterval" : 300000, "concurrency" : 1, "isEventTime" : false, "lateTolerance" : 1000, "qos" : 0, "restartStrategy" : { "attempts" : 0, "delay" : 1000, "jitter" : 0.1, "maxDelay" : 30000, "multiplier" : 2 }, "sendError" : true, "sendMetaToSink" : false }, "sql" : "SELECT labelImageRknn(image) as label FROM jsonimage" } 二进制输入格式处理 与 Json 格式处理差别只有 labelImageRknn(image) --> labelImageRknn(self)，pyairknn 逻辑并没有变化。 { "sql" : "SELECT labelImageRknn(self) as label FROM jsonimage" } pyairknn 核心处理模块 ekuiper 插件 portable 参照官方 pyai 插件，编写特定格式文件一堆，改几个字符就可以。 实现原理 主要逻辑分析如下几个方面： 初始化 rknn load model； 检查是否符合 RV1126 格式； model 载入时间，如果太长，就需要用整机来预编译model； init runtime，不出错即可。 尤其要注意，这些过程要在 5 秒内完成，因为 eKuiper 超时时间是 5 秒，目前版本还不能对开发者可见修改。这里写一个函数 load_rknn_mode() 来实现清晰的代码。 class LabelImageFunc(Function): def __init__(self): load_rknn_model() pass def validate(self, args: List[Any]): if len(args) != 1: return "invalid arg length" return "" def exec(self, args: List[Any], ctx: Context): logging.debug("executing label") return label(args[0]) def is_aggregate(self): return False 核心推理逻辑： label 函数 注意这里的 return top5 格式就是返回给 rule 里面的结果，不支持动态解析，要做好准备； 中间业务逻辑，就是前面说的载入图片到 numpy； 然后给 rknn 模块进行 inference； 最后对推理结果进行格式化处理； def label(file_bytes): logging.info('image object detecting...') encoded = base64.decodebytes(file_bytes.encode("ascii")) input_data = np.array(Image.open(io.BytesIO(encoded))) image = cv2.cvtColor(input_data, cv2.COLOR_BGR2RGB) image = cv2.resize(image, (224, 224)) t0 = time.time() outputs = rknn_lite.inference(inputs=[image]) print('rknn inference time: {:.3f}ms'.format((time.time() - t0) * 1000)) show_outputs(outputs) output = outputs[0][0] output_sorted = sorted(output, reverse=True) labels = load_labels(LABELS) top5 = [] for i in range(5): value = output_sorted[i] index = np.where(output == value) for j in range(len(index)): if (i + j) >= 5: break if value > 0: for k in range(len(index[j])): top5.append({"index": labels[index[j][k]], "value": float(value)}) else: top5.append({"index": "-1", "value": 0.0}) return top5 最终效果 eKuiper 日志打印 如果有错也会在这里出现，便于查看。可以打开 DEBUG 日志，更加详细。 time="2023-02-17 13:16:23" level=info msg="Start source mqttimage instance 0 successfully" file="node/source_node.go:138" rule=rule-mqtt-image time="2023-02-17 13:16:23" level=info msg="new subscription for topic image, reqId is rule-mqtt-image_mqttimage_0" file="mqtt/mqtt_wrapper.go:220" rule=rule-mqtt-image time="2023-02-17 13:16:23" level=info msg="Successfully subscribed to topic image." file="source/mqtt_source.go:111" rule=rule-mqtt-image I:root:image object detecting... rknn inference time: 7.212ms mobilenet_v1 -----TOP 5----- [ 9 83]: 0.2010498046875 [ 9 83]: 0.2010498046875 2023-02-17 13:16:26,316 - /kuiper/plugins/portable/pyairknn/label.py[line:68] - I: image object detecting... Mqtt 输出 测试阶段，使用 mqtt broker 可以实时看到结果，方便调试。 { "label" : [ { "index" : "84:草原鸡、草原松鸡、草原鸡", "value" : 0.2236328125 }, { "index" : "9:母鸡", "value" : 0.2010498046875 }, { "index" : "83:皱褶松鸡、鹧鸪、Bonasa umbellus", "value" : 0.2010498046875 }, { "index" : "9:母鸡", "value" : 0.2010498046875 }, { "index" : "83:皱褶松鸡、鹧鸪、Bonasa umbellus", "value" : 0.2010498046875 }, { "index" : "139:大鸨", "value" : 0.10638427734375 }, { "index" : "87:鹧鸪", "value" : 0.0626220703125 } ] } YiCLOUD IoT 平台输出 还可以通过脚本或插件方式，将结果输出到 IoT 平台，这里以 YiCLOUD 平台为例。
目录 烧录固件 配置 SSH 登录环境 安装 Docker 运行环境 安装 EdgeX 环境 烧录固件 启动为 recovery 模式； 使用 RKDevTool 烧写所需要的固件； 配置 SSH 登录环境 进入 ADB adb shell 修改 sshd_config，增加 root 登录 PermitRootLogin yes 修改 root 密码（可选） passwd 重启 SSHD systemctl restart sshd 登录 SSH 服务器 安装 Docker 运行环境 安装 curl apt install curl 使用脚本安装 Docker curl -fsSL https://get.docker.com -o get-docker.sh sh get-docker.sh 安装完成大概需要 400M 存储空间。 检查 Docker 安装情况 docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 安装 EdgeX 环境 下载 edgex compose git clone https://github.com/edgexfoundry/edgex-compose.git 切换 v2.3.0 版本 git checkout v2.3.0 启动 portainer 容器管理器 修改 portainer 配置文件，127.0.0.1 -> 删除 make portainer 检查 portainer 运行 docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES fa14745d381a portainer/portainer-ce "/portainer -H unix:…" 2 minutes ago Up 2 minutes 8000/tcp, 9443/tcp, 0.0.0.0:9000->9000/tcp portainer 登录 web 跑起 EdgeXFoundry 修改镜像，从私有库下载 cd compose-builder make run # 选择需要的容器镜像 查看 Docker 容器运行情况 docker ps 此处，应该显示所有的 已选择 容器。
技术｜RV1126 本地运行 rnkk-toolkit-lite，用 eKuiper 实现 python 视频 AI 推理
接着上一篇文章：技术｜RV1126 本地安装 rnkk-toolkit-lite，实现 python 实施 AI 推理，本文讲述如何在 RV1126 上实现视频流推理； 同时，有两种方法来实现视频分析，1:通过视频采集分析；2:通过定时图片采集分析。这里主要讲述通过 eKuiper 1.8.0 来实现图片定时抓取分析。 前提条件 准备好 RV1126 运行环境 你需要有一个可以运行 rknn 推理框架的硬件设备。 可参照上一篇文章：技术｜RV1126 本地安装 rnkk-toolkit-lite，实现 python 实施 AI 推理 搭建运行环境； 通过开放源码自己编译运行环境：https://github.com/yiqisoft/rv1126-rknn_toolkit_lite-1.7.1； 通过 Docker Hub 仓库运行环境； 如果实在无法运行，可以寻求我司的技术支持：18616669123 基础知识准备 熟悉 RKNN 相关的配置和运行环境，可参考官方文档：https://github.com/rockchip-linux/rknn-toolkit； 高性能 PC，基于 Windows 用于预编译 RKNN 模型，或基于 macOS 用于开发； Docker 开发构建 docker 镜像和运行容器的技能； Linux开发搭建HOST运行环境的技巧； Python脚本调试Python开发技巧； AI/ML 开发深度学习调试技能； GO语言编程技巧； 也就是说，你最好是一位全栈开发者。 配置过程 过程稍有些复杂和繁琐，需要的基础知识很多，请多参考各框架的官方文档。 搭建 eKuiper 运行环境 基础运行环境 官方文档在此：LF Edge eKuiper - 超轻量物联网边缘流处理软件; 主要是 使用 Python 函数插件运行 AI 算法 章节和 视频源 章节; 使用 eKuiper 官方环境，成功运行 pyai 例程在 tflite 环境下跑通推理； 创建视频分析 stream 和 rule 创建视频源 source 创建规则 rule action 可以根据自己的需求，选择合适自己的 Sink 观察结果（mqtt broker） 确保视频源采集成功，且将图片二进制发送出去； 部署 rknn 推理插件 通过开放源码仓库：https://github.com/yiqisoft/rv1126-rknn_toolkit_lite-1.7.1，下载 pyairknn 代码，修改成你需要的功能或不改； 打包压缩成 zip 文件； 上传到 eKuiper Extension 下； 确保 eKuiper 后台程序没有出错，且 RKNN 环境测试后模型正常。 创建 rknn 推理 rule 创建 rule 启动 rule，查看日志，推理成功 验证外部 mqtt broker 接收到推理结果 总结 必须很熟悉 eKuiper 运行环境，熟读官方文档，这个很重要； Docker 环境要玩的好，否则日志都不一定找得到； 插件不能有一点错误，否则看不到任何结果，代码质量要求很高； 基础运行环境的依赖库很头疼，尤其是在 RV1126 的架构是 armv7l 情况下； eKuiper 的 rule 规则用 SQL 来写，还是很有挑战性的工作，各种参数传递，没点能力理解不了； eKuiper 和 eKuiper manager 版本要一致，1.8.0； 算法精度需要自己多琢磨，可能需要训练自己的模型才能达到毕竟好的推理结果； 最后看一下 tflite 在 CPU 上推理 和 rknn 在 NPU 上推理结果对比，快了 100倍；
技术｜RV1126 本地安装 rnkk-toolkit-lite，实现 python 实施 AI 推理
RV1126 支持4核CPU（armv7l, 32位架构，很多应用不一定支持），2Tops算力（4路IPC摄像头推理，不在话下），低功耗无风扇，是低端 边缘计算 + AI 应用的低成本选择； rknn-toolkit-lite 支持在 RV1126 上跑 python 应用，方便快速开发和部署； 我司的 边缘计算+AI 网关 YiGATE A600 就是基于此架构设计而成。 1. 准备工作 RV1126 开发板或整机 一台整机是必不可少的要求，可以从网店购买； 安装好支持 docker 环境的 debian 或 ubuntu系统； X86或ARM64 开发机 最好是 X86 的宿主主机开发，否则交叉编译环境不好处理； ARM64 主机开发比较麻烦，要慎重选择； RV1126 SDK 找设备供应商获取 RV1126 的 SDK 开发包； 搭建开发环境请参考开发包说明； 2. docker 环境 ubuntu 安装 docker 环境 参照 docker 官方手册，几条命令安装好； 如果需要的话，安装一个 portainer 容器管理器，方便远程 web 管理 image 镜像和容器； 编辑自己的 Dockerfile，用于编译 RV1126 上的运行微服务环境； RV1126 的 rknn-toolkit-lite 需要的环境很特殊，需要 opencv 3.4.3 运行库（SDK版本限制了）； 自己的 Dockerfile，可以随意选择需要的安装包，自己编译 docker image，占用空间可控； 3. 示例代码 Dockerfile 参考示例 FROM debian:10-slim ARG OPENCV_VERSION ARG DEBIAN_FRONTEND=noninteractive WORKDIR /opt/build RUN set -ex \ && apt-get -qq update \ && apt-get -qq install -y --no-install-recommends \ build-essential cmake \ gcc g++ \ curl wget unzip \ libhdf5-103 libhdf5-dev \ libopenblas-base libopenblas-dev \ libprotobuf17 libprotobuf-dev \ libjpeg62-turbo libjpeg62-turbo-dev \ libpng16-16 libpng-dev \ libtiff5 libtiff-dev \ libwebp6 libwebp-dev \ libopenjp2-7 libopenjp2-7-dev \ libtbb2 libtbb-dev \ libeigen3-dev \ tesseract-ocr tesseract-ocr-por libtesseract-dev \ python3 python3-pip python3-dev \ && wget -q --no-check-certificate https://github.com/opencv/opencv/archive/${OPENCV_VERSION}.zip -O opencv.zip \ && wget -q --no-check-certificate https://github.com/opencv/opencv_contrib/archive/${OPENCV_VERSION}.zip -O opencv_contrib.zip \ && unzip -qq opencv.zip -d /opt && rm -rf opencv.zip \ && unzip -qq opencv_contrib.zip -d /opt && rm -rf opencv_contrib.zip \ && cmake \ -D CMAKE_BUILD_TYPE=RELEASE \ -D CMAKE_INSTALL_PREFIX=/usr/local \ -D OPENCV_EXTRA_MODULES_PATH=/opt/opencv_contrib-${OPENCV_VERSION}/modules \ -D EIGEN_INCLUDE_PATH=/usr/include/eigen3 \ -D OPENCV_ENABLE_NONFREE=ON \ -D WITH_JPEG=ON \ -D WITH_PNG=ON \ -D WITH_TIFF=ON \ -D WITH_WEBP=ON \ -D WITH_JASPER=ON \ -D WITH_EIGEN=ON \ -D WITH_TBB=ON \ -D WITH_LAPACK=ON \ -D WITH_PROTOBUF=ON \ -D WITH_V4L=OFF \ -D WITH_GSTREAMER=OFF \ -D WITH_GTK=OFF \ -D WITH_QT=OFF \ -D WITH_CUDA=OFF \ -D WITH_VTK=OFF \ -D WITH_OPENEXR=OFF \ -D WITH_FFMPEG=OFF \ -D WITH_OPENCL=OFF \ -D WITH_OPENNI=OFF \ -D WITH_XINE=OFF \ -D WITH_GDAL=OFF \ -D WITH_IPP=OFF \ -D BUILD_OPENCV_PYTHON3=ON \ -D BUILD_OPENCV_PYTHON2=OFF \ -D BUILD_OPENCV_JAVA=OFF \ -D BUILD_TESTS=OFF \ -D BUILD_IPP_IW=OFF \ -D BUILD_PERF_TESTS=OFF \ -D BUILD_EXAMPLES=OFF \ -D BUILD_ANDROID_EXAMPLES=OFF \ -D BUILD_DOCS=OFF \ -D BUILD_ITT=OFF \ -D INSTALL_PYTHON_EXAMPLES=OFF \ -D INSTALL_C_EXAMPLES=OFF \ -D INSTALL_TESTS=OFF \ /opt/opencv-${OPENCV_VERSION} \ && make -j$(nproc) \ && ln -s /usr/local/lib/python3.7/site-packages/cv2 /usr/local/lib/python3.7/dist-packages/cv2 \ && make install \ && rm -rf /opt/build/* \ && rm -rf /opt/opencv-${OPENCV_VERSION} \ && rm -rf /opt/opencv_contrib-${OPENCV_VERSION} \ && apt-get -qq remove -y \ software-properties-common \ libhdf5-dev \ libprotobuf-dev \ libjpeg62-turbo-dev \ libpng-dev \ libtiff-dev \ libwebp-dev \ libopenjp2-7-dev \ libtbb-dev \ libtesseract-dev \ && apt-get -qq autoremove \ && apt-get -qq clean 编译 docker image --build-arg OPENCV_VERSION=3.4.3，其中 OPENCV_VERSION 为你需要的版本号，这里根据 SDK 里面的链接库选择 3.4.3 --platform=linux/arm/v7，你所需要的平台架构 用 X86 或 ARM64 交叉编译（推荐，速度快很多，具体看机器配置，MacBook pro M1 Max 大概10分钟） docker buildx build --platform=linux/arm/v7 --build-arg OPENCV_VERSION=3.4.3 . -t debian10-python3-opencv3.4.3 用 RV1126 开发板本地编译（至少需要1个小时） docker build --build-arg OPENCV_VERSION=3.4.3 . -t debian10-python3-opencv3.4.3 运行容器 /dev/dri/card0 是 RV1126 的 算力模块的设备，需要映射到容器中； /opt/devel 是 RV1126 上的 rknn-toolkit-lite 的工具包； docker run -it --rm --name rv1126 --privileged -v /dev/dri/card0:/dev/dri/card0 -v /opt/devel/:/opt/devel debian10-python3-opencv3.4.3 /bin/bash 4. 安装 rknn-toolkit-lite 环境和运行结果 安装 rknn-toolkit-lite 环境 Dockerfile 示例 FROM debian10-python3-opencv3.4.3 RUN apt install libdrm-dev -y RUN /usr/bin/python3 -m pip install --upgrade pip RUN pip3 install setuptools COPY ./rknn_toolkit_lite-1.7.1-cp37-cp37m-linux_armv7l.whl /tmp RUN pip3 install /tmp/rknn_toolkit_lite-1.7.1-cp37-cp37m-linux_armv7l.whl RUN rm -rf /tmp/rknn_toolkit_lite-1.7.1-cp37-cp37m-linux_armv7l.whl COPY ./lib/* /usr/lib 编译容器 docker buildx build --platform=linux/arm/v7 . -t rv1126 运行容器 docker run -it --rm --name rv1126 --privileged -v /dev/dri/card0:/dev/dri/card0 -v /opt/devel/:/opt/devel rv1126 /bin/bash 运行效果展示 效果展示 似乎耗时有点久了，整个过程花了25秒左右。 root@743eeb4de2f7:/opt/devel/rknn-toolkit/rknn-toolkit-lite/examples/inference_with_lite# time python3 test.py --> list devices: ************************* None devices connected. ************************* done --> query support target platform ************************************************** Target platforms filled in RKNN model: ['RV1109'] Target platforms supported by this RKNN model: ['RV1109', 'RK1109', 'RV1126', 'RK1126'] ************************************************** done --> Load RKNN model done --> Init runtime environment librknn_runtime version 1.7.1 (97198ce build: 2021-11-24 09:32:17 base: 1131) done --> get sdk version: ============================================== RKNN VERSION: API: librknn_runtime version 1.7.1 (97198ce build: 2021-11-24 09:32:17 base: 1131) DRV: 6.4.6.5.351518 ============================================== done --> Running model resnet18 -----TOP 5----- [812]: 0.9993900656700134 [404]: 0.0004593880439642817 [657 833]: 2.9284517950145528e-05 [657 833]: 2.9284517950145528e-05 [895]: 1.850890475907363e-05 done real 0m24.747s user 0m24.492s sys 0m0.705s 对比 C 编译版本的 yolo 推理 整体耗时只有 0.7秒左右，差距很大啊 root@743eeb4de2f7:/opt/devel/yolov5_detect_demo_release# time ./yolov5_detect_demo librknn_runtime version 1.7.1 (97198ce build: 2021-11-24 09:32:17 base: 1131) time_use is 126.657997 car @ (258 909 583 1109) 0.841335 car @ (534 736 739 859) 0.831931 bus @ (930 413 1206 531) 0.818415 car @ (1766 578 1872 657) 0.810571 car @ (721 760 927 921) 0.792803 car @ (563 950 801 1115) 0.770443 car @ (956 809 1138 968) 0.752421 car @ (801 991 1071 1115) 0.719339 person @ (1431 619 1461 686) 0.699269 car @ (0 495 137 598) 0.674166 person @ (1819 651 1863 739) 0.639357 car @ (1208 431 1261 472) 0.632317 car @ (862 402 936 469) 0.618040 car @ (1781 446 1845 504) 0.575020 person @ (1543 677 1590 771) 0.561444 person @ (375 481 399 528) 0.538842 car @ (1684 431 1737 478) 0.505462 motorbike @ (1508 748 1564 836) 0.475376 person @ (1200 589 1232 677) 0.471330 car @ (1839 451 1878 501) 0.438286 traffic light @ (1587 548 1608 607) 0.411564 car @ (715 390 777 425) 0.394160 truck @ (472 451 745 557) 0.392559 car @ (812 416 874 469) 0.387184 car @ (466 425 575 481) 0.358873 car @ (1622 410 1669 454) 0.350634 car @ (1481 331 1578 378) 0.340664 car @ (1191 357 1235 404) 0.321087 real 0m0.690s user 0m0.454s sys 0m0.191s 注意事项 相比 C 编译的二进制代码，python 用于测试和演示还是比较好的方案； python 脚本运行速度比较慢，尤其是在 rknn init 阶段，大概需要20多秒； inference 速度还是很快的，1秒都不需要； 模型文件转换导出要注意能够支持 RV1126 环境， 如果用 rknn-toolkit-lite 1.7.3 版本，会出现一个 armv7l 不支持的错误，导致无法完成推理：Exception: Unsupported run platform: Linux armv7l --> Load RKNN model done --> Init runtime environment E Catch exception when init runtime! E Traceback (most recent call last): File "/usr/local/lib/python3.7/dist-packages/rknnlite/api/rknn_lite.py", line 144, in init_runtime async_mode=async_mode, rknn2precompile=rknn2precompile) File "rknnlite/api/rknn_runtime.py", line 292, in rknnlite.api.rknn_runtime.RKNNRuntime.__init__ File "rknnlite/api/rknn_runtime.py", line 491, in rknnlite.api.rknn_runtime.RKNNRuntime._load_library File "rknnlite/api/rknn_runtime.py", line 486, in rknnlite.api.rknn_runtime.RKNNRuntime._get_rknn_api_lib_path Exception: Unsupported run platform: Linux armv7l Init runtime environment failed Open Source is here GitHub respository: https://github.com/yiqisoft/rv1126-rknn_toolkit_lite-1.7.1.git Docker Hub respository: https://hub.docker.com/r/jiekechoo/rv1126-rknn_toolkit_lite
什么是余弦相似性 余弦相似性通过测量两个向量的夹角的余弦值来度量它们之间的相似性。0度角的余弦值是1，而其他任何角度的余弦值都不大于1；并且其最小值是-1。从而两个向量之间的角度的余弦值确定两个向量是否大致指向相同的方向。两个向量有相同的指向时，余弦相似度的值为1；两个向量夹角为90°时，余弦相似度的值为0；两个向量指向完全相反的方向时，余弦相似度的值为-1。这结果是与向量的长度无关的，仅仅与向量的指向方向相关。余弦相似度通常用于正空间，因此给出的值为0到1之间。 注意这上下界对任何维度的向量空间中都适用，而且余弦相似性最常用于高维正空间。例如在信息检索中，每个词项被赋予不同的维度，而一个文档由一个向量表示，其各个维度上的值对应于该词项在文档中出现的频率。余弦相似度因此可以给出两篇文档在其主题方面的相似度。 另外，它通常用于文本挖掘中的文件比较。此外，在数据挖掘领域中，会用到它来度量集群内部的凝聚力。 定义 两个向量间的余弦值可以通过使用欧几里得点积公式推导： 给定两个属性向量， A 和B，其余弦相似性θ由点积和向量长度给出，如下所示： 这里的A_i和B_i分别代表向量A和B的各分量。 给出的相似性范围从-1到1：-1意味着两个向量指向的方向正好截然相反，1表示它们的指向是完全相同的，0通常表示它们之间是独立的，而在这之间的值则表示中间的相似性或相异性。 对于文本匹配，属性向量A 和B 通常是文档中的词频向量。余弦相似性，可以被看作是在比较过程中把文件长度正规化的方法。 在信息检索的情况下，由于一个词的频率（TF-IDF权）不能为负数，所以这两个文档的余弦相似性范围从0到1。并且，两个词的频率向量之间的角度不能大于90°。 角相似性 “余弦相似性”一词有时也被用来表示另一个系数，尽管最常见的是像上述定义那样的。透过使用相同计算方式得到的相似性，向量之间的规范化角度可以作为一个范围在[0,1]上的有界相似性函数，从上述定义的相似性计算如下： 在这一公式中，向量系数可能是正，也可能是负。 或者，用以下式子计算 而在这一式子中，向量系数总是正的。 虽然“余弦相似性”一词有时会用来表示这个角距离，但实际上很少这样说，因为角度的余弦值只是作为一种计算角度的简便方法而被用到，本身并不是意思的一部分。角相似系数的优点是，当作为一个差异系数（从1减去它）时，在不为第一种意义的情况下，产生的函数是一个严格距离度量。然而，对于大多数的用途，这不是一个重要的性质。若对于某些情况下，只有一组向量之间的相似性或距离的相对顺序是重要的，那么不管是使用哪个函数，所得出的顺序都是一样的。 与“Tanimoto”系数的混淆 有时，余弦相似性会跟一种特殊形式的、有着类似代数形式的相似系数相混淆： 事实上，这个代数形式是首先被Tanimoto定义，作为在所比较集合由位元向量表示时计算其Jaccard系数的方法。虽然这公式也可以扩展到向量，它具有和余弦相似性颇为不同的性质，并且除了形式相似外便没有什么关系。 Ochiai系数 这个系数在生物学中也叫Ochiai系数，或Ochiai-Barkman系数： 这里A和B是集合，n(A)是A的元素个数。如果集合由位元向量所代表，那么可看到落和系数跟余弦相似性是等同的。 应用 我们还希望找到与原文章相似的其他文章。比如，"Google新闻"在主新闻下方，还提供多条相似的新闻。为了找出相似的文章，需要用到"余弦相似性"（cosine similiarity）。下面，我举一个例子来说明，什么是"余弦相似性"。 简单起见，我们来看看下面的句子： A：我喜欢看电视，不喜欢看电影。 B：我不喜欢看电视，也不喜欢看电影。 请问怎样才能计算上面两句话的相似程度？ 基本思路是：如果这两句话的用词越相似，它们的内容就应该越相似。因此，可以从词频入手，计算它们的相似程度。 第一步：分词 A：我/喜欢/看/电视，不/喜欢/看/电影。 B：我/不/喜欢/看/电视，也/不/喜欢/看/电影。 第二步：列出所有的词、字 我，喜欢，看，电视，电影，不，也 第三步：计算词频 A：我 1，喜欢 2，看 2，电视 1，电影 1，不 1，也 0。 B：我 1，喜欢 2，看 2，电视 1，电影 1，不 2，也 1。 第四步：描述词频向量 A：[1, 2, 2, 1, 1, 1, 0] B：[1, 2, 2, 1, 1, 2, 1] 到这里，问题就变成了如何计算这两个向量的相似程度。 我们可以把它们想象成空间中的两条线段，都是从原点（[0, 0, ...]）出发，指向不同的方向。两条线段之间形成一个夹角，如果夹角为0度，意味着方向相同、线段重合；如果夹角为90度，意味着形成直角，方向完全不相似；如果夹角为180度，意味着方向正好相反。因此，我们可以通过夹角的大小，来判断向量的相似程度。夹角越小，就代表越相似。 以二维空间为例，上图的a和b是两个向量，我们要计算它们的夹角θ。余弦定理告诉我们，可以用下面的公式求得： 假定a向量是[x1, y1]，b向量是[x2, y2]，那么可以将余弦定理改写成下面的形式： 数学家已经证明，余弦的这种计算方法对n维向量也成立。假定A和B是两个n维向量，A是 [A1, A2, ..., An] ，B是 [B1, B2, ..., Bn] ，则A与B的夹角θ的余弦等于： 使用这个公式，我们就可以得到，句子A与句子B的夹角的余弦。 余弦值越接近1，就表明夹角越接近0度，也就是两个向量越相似，这就叫"余弦相似性"。所以，上面的句子A和句子B是很相似的，事实上它们的夹角大约为20.3度。 由此，我们就得到了"找出相似文章"的一种算法： （1）使用TF-IDF算法，找出两篇文章的关键词； （2）每篇文章各取出若干个关键词（比如20个），合并成一个集合，计算每篇文章对于这个集合中的词的词频（为了避免文章长度的差异，可以使用相对词频）； （3）生成两篇文章各自的词频向量； （4）计算两个向量的余弦相似度，值越大就表示越相似。 "余弦相似度"是一种非常有用的算法，只要是计算两个向量的相似程度，都可以采用它。