在上一篇文章中,我们成功地直接在树莓派上运行了一个基于Yolo的深层神经网络,在图片和视频流上半实时地检测对象。处理是在本地完成的,这是本地视频流的最佳选择。但是,如果你有一个农场,它可以是有点太耗电。
以下是树莓派的一些不太容易获得功耗值。您可以轻松地看到,大量 CPU 使用率将能耗翻倍。在这种情况下,一个可能的解决方案是使用易于设置的 lambdas 将处理卸载到服务器上。
您可能还喜欢:
树莓派,OpenCV,深度神经网络,当然还有一点克洛朱雷
您可能以前尝试过 AWS Lambdas,有些人甚至可能在生产中使用一些,它们是无状态函数、处理程序、部署和托管在 AWS 基础设施上。
在这篇文章中,我们将创建和部署(显然)基于克洛朱雷的羔羊,并调用他们从我们心爱的树莓派分析一些图片使用神经网络通过折纸。
lambda 将从本地开发机器开发和部署,最终将从树莓派调用,如下图所示:
要与 AWS 服务交互并设置服务,通常要通过 AWS 命令行界面。
要 awscli
安装,可以使用 apt
:
sudo apt install awscli
但是,根据您的运气和时间,您可能最终得到一个稍微旧的版本,并且可能想要尝试基于 Python 的安装:
sudo pip3 install awscli --upgrade
准备好 AWS 命令后,请确保使用 IAM 用户对其进行配置。
配置 AWS IAM 用户
在 AWS 控制台中,我们只需设置一个新的 IAM 用户;这里 nico
命名。
我们还创建了一个角色 lando
,我们向它授予完全 lambda 访问权限:
然后,检索该新用户的密钥 ID 和访问密钥。我们现在可以从树莓派和我们的开发机器运行 AWS 配置。
您必须回答以下常见问题:
- AWS 访问密钥 ID
- AWS 密钥
- 默认区域名称
- 默认输出格式
目前,这一切都在 AWS 控制台中。
克洛朱雷的第一个兰巴
这是 AWS 网站上关于在 Clojure中编写和部署 lambda 的现有文档的一个简单摘要。
在这里,我们将创建一个新的Clojure项目, lein
使用,编写一个函数,可以调用lambda框架,并部署它
“你怎么做’,你是老海盗?…
你已经习惯了,所以让我们创建一个新的Clojure项目简介:
lein new lando
这为我们提供了如下所示的项目结构:
├── project.clj
├── resources
├── src
│ └── lando
│ ├── core.clj
Project.clj 文件不需要任何特殊内容,因此简而言之:
(defproject lando "0.1.0-SNAPSHOT"
...
:dependencies [
[org.clojure/clojure "1.10.0"]
]
:main lando.core
:profiles {
:uberjar {:aot :all}
}
:repl-options {:init-ns lando.core})
请注意,不需要额外的 AWS 特定库…现在。此外,请注意,我们正在强制提前编译,以便在编译时生成 Java 类。
现在,在core.clj文件本身上,这差不多是 AWS 文档的副本,所以这里没有什么新内容。我们使用 gen-class
来创建将由 lambda 框架调用的 Java 函数。
在这里,函数将采用字符串并返回一个字符串,两者都是 Java 对象。函数的核心是不言自明的;它返回一个字符串”你好”和字符串的内容在参数中传递。
(ns lando.core
(:gen-class
:methods [^:static [handler [String] String]]))
(defn -handler [s]
(str "Hello " s "!"))
我们完成了让我们从中创建一个可部署到 AWS Lambda 的 jar 文件。
lein uberjar
我们现在在目标文件夹中有一个不错的 jar 文件:
ls -al target/lando-0.1.0-SNAPSHOT-standalone.jar
要部署我们的功能,我们使用 AWS CLI 和 create-function
子命令:
aws lambda create-function --function-name core \
--handler lando.core::handler \
--runtime java8 \
--memory 512 \
--timeout 20 \
--zip-file fileb://./target/lando-0.1.0-SNAPSHOT-standalone.jar \
要检查一切正常,可以运行:
aws lambda list-functions
并检查输出包含新部署的函数:
{
"Functions": [
{
"FunctionName": "core",
"FunctionArn": "arn:aws:lambda:ap-northeast-1:817572757560:function:core",
"Runtime": "java8",
"Role": "arn:aws:iam::817572757560:role/lando",
"Handler": "lando.core::handler",
"CodeSize": 22140085,
"Description": "",
"Timeout": 20,
"MemorySize": 512,
"LastModified": "2019-09-20T07:56:11.644+0000",
"CodeSha256": "KvnPmrSwEjWcUvDbhjy2dE2+VxhmjnAHqa2ghzhatMg=",
"Version": "$LATEST",
"TracingConfig": {
"Mode": "PassThrough"
},
"RevisionId": "d699f624-6c6a-4e24-a8aa-15f4ad8da5cc"
}
]
}
通常,如果此时出现问题,是因为缺少 AWS 权限或处理程序函数命名中的拼写错误。
使用多个配置文件时,通过设置环境变量,确保正在使用的配置文件是所需的 AWS_PROFILE
配置文件。
export AWS_PROFILE="default"
最复杂的是完成;现在,我们可以用子命令调用 lambdainvoke
调用函数是通过 完成。 invoke
aws lambda invoke --function-name lando --payload '["Lando"]' lando.log
呼叫的状态在完成时显示:
{
"StatusCode": 200,
"ExecutedVersion": "$LATEST"
}
调用本身的结果在lando.log文件中:
$ cat lando.log
"Hello Lando!"
“你怎么做’,你是老海盗?…
通过调用 OpenCV/Oraami 来加快事情
让我们来看看它是如何通过使用我们最喜欢的克洛朱雷成像库,折纸。
我们的例子将很容易。我们只是输出正在使用的 OpenCV 版本,这也意味着所有必需的本机库都已正确加载,可以从 lambda 上下文中加载。
在project.clj的依赖项部分中,让我们添加新的依赖项:
:dependencies [
[org.clojure/clojure "1.10.0"]
[origami "4.1.1-6"]
]
让我们在同一项目中创建一个新的命名空间 origami
lando
,使用以下代码:
(ns lando.origami
(:require [opencv4.core :refer :all])
(:gen-class
:methods [^:static [handler [String] String]]))
(defn -handler [s]
(str "Using OpenCV Version: " VERSION ".."))
在代码中还没有令人讨厌的东西。我们可以运行 lein uberjar
以创建 jar 文件,然后 create-function
使用相同的角色从 CLI 运行 。
aws lambda create-function --function-name origami \
--handler lando.origami::handler \
--runtime java8 \
--memory 512 \
--timeout 20 \
--zip-file fileb://./target/lando-0.1.0-SNAPSHOT-standalone.jar \
这一次,事情乍一看不太顺利…我们被卡住了:
An error occurred (RequestEntityTooLargeException) when calling the CreateFunction operation: Request must be smaller than 69905067 bytes for the CreateFunction operation
让我们检查生成的 jar 文件的大小:
$ ls -alh target/lando-0.1.0-SNAPSHOT-standalone.jar
-rw-r--r-- 1 niko staff 95M Sep 20 17:05 target/lando-0.1.0-SNAPSHOT-standalone.jar
这将略高于 AWS 强制实施的 70M 限制。所以,你可能不知道的是,折纸有一个版本的OpenCV为每个不同的平台,它应该运行。这样,每个人都可以很快地开始,但它使生成的独立 jar 文件相当大。
$ unzip -l target/lando-0.1.0-SNAPSHOT-standalone.jar | grep natives
0 12-17-2018 10:27 natives/linux_32/
0 08-02-2019 11:18 natives/linux_64/
39529136 08-02-2019 11:18 natives/linux_64/libopencv_java411.so
0 08-14-2019 10:11 natives/linux_arm/
25907540 08-14-2019 10:12 natives/linux_arm/libopencv_java411.so
0 08-02-2019 13:54 natives/linux_arm64/
32350952 08-02-2019 13:55 natives/linux_arm64/libopencv_java411.so
0 08-01-2019 14:47 natives/osx_64/
82392636 08-01-2019 14:47 natives/osx_64/libopencv_java411.dylib
0 12-17-2018 10:27 natives/windows_32/
0 08-01-2019 16:36 natives/windows_64/
55030784 08-01-2019 16:37 natives/windows_64/opencv_java411
e linux_64/libopencv_java411.so.
让我们稍微苗条一点:
zip -d target/lando-0.1.0-SNAPSHOT-standalone.jar \
natives/windows_64/opencv_java411.dll \
natives/osx_64/libopencv_java411.dylib \
natives/linux_arm64/libopencv_java411.so \
并再次检查独立罐的大小:
$ ls -alh target/lando-0.1.0-SNAPSHOT-standalone.jar
-rw-r--r-- 1 niko staff 21M Sep 20 17:14 target/lando-0.1.0-SNAPSHOT-standalone.jar
好多了现在,我们可以再次重新部署折纸功能,并调用它:
aws lambda invoke --function-name origami --payload '""' origami.log
$ cat origami.log
"Using OpenCV Version: 4.1.1.."
在 Lambda 上运行我们的深层神经网络
现在您可能已经习惯了,如果您按照使用Clojure 运行 DNN的系列文章,我们几乎可以包装之前的代码,然后从 lambda 处理程序调用它。这一次,该函数将创建网络,并在 lambda 的输入上运行它,作为我们想要找出其中的内容的图片的 URL。这次我们将此代码放入名为lando.dnn的新命名空间中。
(ns lando.dnn
(:require
[origami-dnn.net.yolo :as yolo]
[opencv4.core :refer :all]
[opencv4.utils :as u]
[origami-dnn.core :as origami-dnn])
(:gen-class
:methods [^:static [handler [String] String]]))
(defn result! [result labels]
(let [img (first result) detected (second result)]
(map #(let [{confidence :confidence label :label box :box} %]
{:label (nth labels label) :confidence confidence }) detected)))
(defn run-yolo [ input ]
(let [[net opts labels] (origami-dnn/read-net-from-repo "networks.yolo:yolov2-tiny:1.0.0")]
(println "Running yolo on image:" input)
(-> input
(u/mat-from-url)
(yolo/find-objects net)
(result! labels))))
(defn -handler [s]
(apply str (run-yolo s)))
在上面的代码片段中,请注意,我们只对此示例感兴趣,该示例位于找到的对象、其标签以及与找到的对象关联的置信度上。结果!方法用于格式化 返回的结果, find-objects
并替换 blue-boxes
直接在图片上绘制的通常方法。
项目现在需要一个新的依赖 origami-dnn
项,只需添加:
:dependencies [
[org.clojure/clojure "1.10.0"]
[origami-dnn "0.1.5"]
]
这一次,在部署时,我们确实需要指定一个 Java 环境变量,该变量用于 origami-dnn
将网络文件扩展到更方便的地方, /tmp
此处,该变量位于同一 lambda 的倍数之间,从而避免了一遍又一遍地检索文件。
这是使用 开关 JAVA_TOOL_OPTIONS
–environment
完成的。
aws lambda create-function --function-name dnn \
--handler lando.dnn::handler \
--runtime java8 \
--memory 512 \
--timeout 20 \
--zip-file fileb://./target/lando-0.1.0-SNAPSHOT-standalone.jar \
--role arn:aws:iam::817572757560:role/lando \
--environment Variables="{JAVA_TOOL_OPTIONS=-Dnetworks.local=/tmp}"
函数需要一张图片,所以很明显我们会找到一只猫的图片…
com.cn/wp-内容/上传/2019/10/lambda2-2.jpg”/*
并将其馈送函数调用。
aws lambda invoke \
--function-name dnn \
--payload '"https://images.unsplash.com/photo-1518791841217-8f162f1e1131"' \
dnn.log
我们会很高兴地知道,这是一只猫:
$ cat dnn.log
"{:label \"cat\", :confidence 0.7528027}"
伟大。您可以使用几张不同的图片来尝试,您可能还注意到,即使第一次运行相当缓慢,因为它需要获取和扩展已编译的网络文件,后续运行可以重用这些文件,然后,网络会立即加载。
在映像列表上运行 DNN 并返回 JSON
“你可能想扣起来,宝贝。
最后一个示例基于以前的图像检测 lambda,但以图像数组作为输入。然后,它返回一个格式良好的 JSON 答案,其中包含每个输入的检测结果。
克洛朱尔的柴郡将添加到项目.clj,以便用于生成生成的 JSON。
[cheshire "5.9.0"]
我们将创建一个 lando.dnn2
命名空间,它从 的副本粘贴 lando.dnn
开始,并带来一些更新。
首先,生成的类具有略有不同的方法签名;我们告诉框架,输入将是一个列表,而不是一个字符串。
(:gen-class
:methods [^:static [handler [java.util.List] String]])
其次,我们把网络的编制工作移到了声明之外的子函数之外,使事情稍快一些。
(let [[net opts labels] (origami-dnn/read-net-from-repo "networks.yolo:yolov2-tiny:1.0.0")]
(defn result! [result labels]
...)
(defn run-yolo [ input ]
...)
)
最后,我们使用柴郡 generate-string
返回格式正确的 JSON。
(generate-string (doall (map run-yolo s)))
最后,完整的 Clojure 命名空间为我们提供了:
(ns lando.dnn2
(:require
[origami-dnn.net.yolo :as yolo]
[opencv4.core :refer :all]
[opencv4.utils :as u]
[origami-dnn.core :as origami-dnn]
[cheshire.core :refer [generate-string]])
(:gen-class
:methods [^:static [handler [java.util.List] String]]))
(let [[net opts labels] (origami-dnn/read-net-from-repo "networks.yolo:yolov2-tiny:1.0.0")]
(defn result! [result labels]
(let [img (first result) detected (second result)]
(doall (map #(let [{confidence :confidence label :label box :box} %]
{:label (nth labels label) :confidence confidence }) detected))))
(defn run-yolo [ input ]
(println "Running yolo on image:" input)
(-> input
(u/mat-from-url)
(yolo/find-objects net)
(result! labels))))
(defn -handler [s]
(println (first s))
(generate-string (doall (map run-yolo s))))
部署与之前完全相同,但更新的函数名称除外。现在,我们有 dnn2
处理程序是:lando
要发送到函数的有效负载也超长。但基本上,这是一个具有不同图像的数组,我们希望在上运行对象检测。
aws lambda invoke \
--function-name dnn2 \
--payload '["https://image.cnbcfm.com/api/v1/image/105992231-1561667465295gettyimages-521697453.jpeg?v=1561667497&w=630&h=354","https://a57.foxnews.com/static.foxnews.com/foxnews.com/content/uploads/2019/07/931/524/creepy-cat.jpg","https://a57.foxnews.com/static.foxnews.com/foxnews.com/content/uploads/2019/07/931/524/creepy-cat.jpg","https://images.unsplash.com/photo-1518791841217-8f162f1e1131?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=900&q=60"]' \
dnn2.log
由于 lambda 需要检索和扩展网络文件,因此第一次调用相当缓慢:
$ cat dnn2.log
"[[{\"label\":\"dog\",\"confidence\":0.7746848}],[{\"label\":\"cat\",\"confidence\":0.6056155}],[{\"label\":\"cat\",\"confidence\":0.6056155}],[{\"label\":\"cat\",\"confidence\":0.78931814}]]"
最后,一切都是关于猫和狗的…
希望你喜欢!在评论部分告诉我们您的想法!