如果您曾经想尝试计算机视觉,但没有时间设置复杂的开发环境,那么本教程适合您。在某些情况下,计算机视觉可以很好地替代物理传感器,尤其是在计算物体时。
在本教程中,我将引导您完成我们发布的演示应用程序,该应用程序使用伦敦的交通摄像头通过车辆计数来评估当前的拥堵情况。它基于我们创建的可重复使用的项目模板,以帮助您复制项目。然后,我将向您展示如何使用此模板创建您自己的项目副本,并在 Quix(一种用于开发和运行事件流应用程序的工具)中启动并运行它。
我们的计算机视觉应用程序演示版本还使用托管在Confluence Cloud (完全托管的 Apache Kafka 作为服务)中的消息代理,但并不强制要求拥有 Confluence Cloud 帐户才能学习本教程。
最终结果应如下所示:
您可以在以下地址实时试用此演示版本: https://app-demo-computervisiondemo-prod.deployments.quix.ai/
这个应用程序在做什么?
该应用程序使用伦敦交通摄像头(也称为“Jam Cams”)的实时反馈来计算车辆数量并估计拥堵程度。然后,它使用视觉指示器显示伦敦地图上发生拥堵的位置。使用机器学习模型对图像(而不是传感器或 GPS 数据)中的对象进行检测来对车辆进行计数。
对象检测模型还将车辆分为不同类型,您可以按这些类型过滤数据。
例如,您可以使用“选择对象”下拉列表仅查看所有交通摄像头在当前时间点检测到的公交车数量。
请注意,该应用程序不会计算全天观察到的所有车辆,而仅计算当前时间(稍后会详细介绍)。
为什么使用对象检测来确定拥塞?
因为其他方法并不总是可靠。例如,2020 年,一位柏林艺术家仅用一辆手推车和 99 部二手手机,成功地在施普雷河上的一座主桥上创造了一场“虚拟”交通堵塞。随后,谷歌地图尽职尽责地将该区域显示为高度拥堵。
因此,通常使用其他类型的数据来增强基于 GPS 的拥堵估计。这包括历史模式、传感器数据、有关计划关闭的市政信息和用户报告的事件。然而,最可靠的交叉引用之一是从交通摄像头收集的拥堵视觉识别( 假设海鸥没有挡住视线)。
尽管存在错误的海鸥,但政府组织现在正在使用计算机视觉来增强交通数据并提高交通量估计的准确性。例如,去年9月22日,加拿大统计局发表了一篇题为“根据交通摄像头图像进行交通量估算:走向实时交通数据流”的论文,提出了一种基于计算机视觉的系统,可以定期从加拿大交通摄像头中提取车辆数量图像。
现在有了 Quix,您不需要一个研究科学家团队来尝试这样的事情。任何有能力的开发人员都可以尝试一下,并在几分钟内启动并运行。但在本例中,我们谈论的更像是 60 分钟而不是 5 分钟。毕竟这是一个大工程!
要重现该项目,您需要两件事:
Traffic for London API的 API 密钥(有关更多详细信息,请参阅此项目的文档)
一个免费的 Quix 帐户 - 如果您还没有创建帐户,您可以立即注册(您可以使用现有的 Google、GitHub 或 Microsoft 帐户,只需点击几下即可完成注册)。
获取项目(以及我们的任何演示应用程序)副本有几个主要步骤:
从 GitHub 分叉我们的计算机视觉演示存储库。
这将使您可以轻松自定义项目版本,但仍然受益于上游改进。
在 Quix Cloud 中创建一个项目,然后创建一个新的开发环境并将其链接到您的分支。
这将允许您在自己的帐户下运行和更新 Quix Cloud 中的应用程序。
更新 TfL 相机 API 和 Google 地图等外部服务的凭据。
API 密钥等机密不会传输到项目副本中,因此您需要自己添加这些内容。
设置好基础知识后,我们将深入研究代码并了解如何调整它。
为了掌握代码,我们首先分叉计算机视觉演示存储库。为什么要分叉而不是克隆?因为稍后您将将该代码引入您自己的 Quix 环境,而使用 fork 是保持环境同步的最简单方法。您还可以获取我们对项目模板所做的任何上游更改。
为了简单起见,我假设您已经有一个 GitHub 帐户。但是,您可能想为此项目创建特定的 Git 用户。稍后您将向 Quix 授予对存储库的 SSH 访问权限,并且拥有单独的用户是确保 Quix 不会拥有超出应有访问权限的好方法。
确保分叉所有分支(在 GitHub 的分叉向导中,取消选择“仅复制主分支”)。这是因为,如果您使用试用帐户,则需要一个备用分支来在 Quix Cloud 中创建开发环境。
在 Quix 中创建环境之前,您首先需要创建一个项目。在项目创建向导过程中,系统会要求您添加初始环境。一旦掌握了窍门,您可以稍后添加更多环境。
要创建项目并将环境连接到您的分叉存储库,请按照以下步骤操作:
登录 Quix 并单击 +新项目。
将您的项目命名为“我的计算机视觉演示”(或类似名称),然后选择“连接到您自己的 Git存储库”。
在下一个屏幕上,您应该会看到一些有关如何将 Quix SSH 密钥添加到您的存储库的说明 - 请按照这些说明进行操作。添加此密钥使 Quix 能够自动将您的存储库与 Quix 环境同步
在下一个屏幕上,系统会要求您创建一个环境,该环境使您能够并行部署来自不同分支的代码。
输入“ tutorial
”作为环境名称,然后从分叉的存储库中选择“ tutorial
”分支。
继续完成项目创建向导中的后续步骤。
向导会询问您要使用哪个消息代理。该项目的原始版本使用Confluence Cloud作为消息代理。如果您想使用 Confluence Cloud,您首先需要有一个帐户 - 在这种情况下,您需要选择“连接到您的 Confluence Cloud”并输入您的凭据。
但是,使用 Confluence Cloud 并不是强制性的。对于本教程,您还可以坚持使用默认的 Quix 消息代理。
完成向导后,导航到“管道”页面(如果尚未打开)。您将看到一条标准警告,表明您的 Quix 环境与源存储库不同步(因为环境开始为空)。
单击“同步环境”按钮从分叉存储库中提取最新代码。注意:同步过程是双向的,因此如果您更改环境中的某些代码,它也会将其推回源存储库。
希望同步成功。如果成功,您应该会看到所有服务开始在“管道”页面上构建。
服务相当多,因此所有服务构建并开始运行需要几分钟的时间。
请注意,S3 服务默认停止,因为您需要自己的 AWS 帐户才能使该服务正常运行。但无论如何,这对于本教程来说并不是真正必要的。
要在“管道”页面上查看完整的管道,请单击并拖动画布空白部分上的任意位置,然后向右滚动,或者按住 Ctrl / ⌘ 并使用鼠标滚轮进行缩小。
滚动直到您可以看到名为“Project Front End”的服务。
单击“Project Front End”服务名称旁边的蓝色启动图标。
您现在应该看到您自己的计算机视觉应用程序副本,可以使用了。
管道由许多服务组成,但架构可以抽象为三个主要部分,如下图所示:
第一组服务 (1-3) 利用伦敦的交通摄像头,识别每个受监控路段上的车辆。
第二组服务 (4-7) 保存每个路段的车辆类型的运行总数,并记录给定帧中检测到的最大车辆数量。然后,该数据被缓冲并传递到 REST API 服务,以便任何想要请求数据的外部服务都可以访问数据。
最终服务 (8) 托管一个前端,该前端轮询 REST API 以获取汇总的车辆统计数据,并在 Websocket 上侦听来自所有交通摄像头(例如视频帧)的实时原始数据,这些数据来自 Quix 中的主题。这种轮询数据和实时数据的组合用于在伦敦地图上可视化交通水平。
我不会详细介绍各个服务的细节,因为文档已经在这方面做得很好。但如果您想了解它们的工作原理,这里有一些参考信息以及文档链接。
如果单击服务名称,您可以看到在 Quix 只读环境中运行的实际服务,包括运行时日志和数据沿袭。
服务名称 | 描述 |
---|---|
我在这里想重点介绍的是如何根据您自己的要求定制项目。
为了帮助您自定义项目,我将向您展示如何对后端的聚合逻辑进行小更改并在前端呈现新信息。
之后,我将向您介绍一些外部资源,这些资源将帮助您执行更强大的任务,例如车辆计数和对象跟踪。但首先我们需要进行一些管理,例如添加新的应用程序机密。
项目模板配置了一些默认凭据,但您需要更改它们才能使项目副本正常工作。您需要将每个凭据定义为项目中的秘密。秘密如下。
前端与 Quix 中的 SignalR 集线器通信的承载令牌(密钥:' bearerToken
')
您的 Tfl API 密钥(密钥:“ tfl_api_key
”)
前端使用 SignalR 客户端库与 Quix 进行通信(通过Websockets API ),以检索和呈现来自后端的数据。此 API 需要不记名令牌来验证客户端应用程序。
在本教程中,您将创建一个 Quix 个人访问令牌作为您的不记名令牌。然后,您将创建一个秘密来将此令牌存储在您的环境中(是的,这有点复杂,但您只需执行一次)。
#获取个人访问令牌
以下是您在 Quix 中获取个人访问令牌的方法。
打开右上角的个人资料菜单,然后选择个人访问令牌。
在出现的对话框中,单击生成令牌并将您的个人访问令牌粘贴到记事本或任何其他临时存储位置 - 下一步将需要它。
在 Quix 门户中,打开应用程序页面并单击Sentiment Demo UI以打开 Quix IDE。
在“变量”部分(左下角)中,单击“机密管理” 。
在出现的侧栏中,单击+ New Secret ,然后输入“ bearerToken
”作为密钥。
在“默认”和“教程”列中,将您在上一步中创建的个人访问令牌粘贴为每个单元格中的值。
假设您已在Tfl API 门户注册,您首先还需要添加您自己的 Tfl API 密钥作为秘密。
tfl_api_key
”添加秘密。
目前,您只能看到过去 24 小时内每个摄像头观察到的最大车辆数量。例如,让我们检查一下从 Kings Cross 和 Swinton Street 的摄像机拍摄的视频帧下方显示的数据
我们不知道何时观察到由 11 辆车组成的集群,只知道观察是在过去 24 小时内的某个时间进行的。
但看到整个伦敦的相同数据不是很有趣吗?即,伦敦的摄像机在任一时间观察到的车辆的最大总数量是多少?所有摄像机观测到的公交车的最大数量是多少?
为了回答这些问题,我们希望最终得到如下所示的数据
{"24hmax_vehicles_allcams": 680.0, "24hmax_buses_allcams": 131.0, "24hmax_cars_allcams": 522.0, "24hmax_trucks_allcams": 94.0, "24hmax_motorcycles_allcams": 4.0}
请注意,我们这里讨论的不是车辆总数(我稍后会讨论),只是伦敦交通摄像头在过去 24 小时内观察到的最多车辆的快照。
要获取此数据,您需要进行以下更改
获取过去 24 小时内观察到的每种车辆类型的最大值(不仅仅是所有车辆)。
存储最新的最大值,将它们全部聚合(跨所有摄像机)。
当不同摄像机观察到新的最大值时,不断刷新聚合。
然后,您将在前端渲染数据,使其看起来像这样:
我已经为此创建了一些代码,但在测试之前,您需要一个地方来存储新聚合的引入。在这个示例中,我将向您展示如何使用新的 kafka 主题来存储数据。
不完全确定主题是什么? Apache Kafka 文档是一个很好的起点,但本质上,主题被描述为类似于文件系统中的文件夹,而事件(以消息的形式)是该文件夹中的文件。您将学习如何在 Quix UI 中创建一个 - 这是一个非常简单的过程。
要在 Quix 门户中创建主题,请执行以下步骤:
在 Quix 门户中,打开“主题”页面,然后单击右上角的“新增” 。
在出现的对话框中,输入名称,例如“ max-vehicles-agg ”,保留默认设置不变,然后单击“完成” 。
现在,您需要更新后端的代码来编写此主题。您需要更改的服务称为“最大车辆窗口”。它是一项使用Quix Streams和 Pandas Python 库聚合数据的 Python 服务。
一般来说,编辑服务时,您始终有两个选择。
编辑并测试您的本地 IDE,然后提交更改并将其推送到您的分叉存储库。
在在线 Quix IDE 中编辑和测试。
Quix IDE 可能会更快一些,因为所有依赖项都已为您安装,并且您无需设置新的虚拟环境。它还会自动推送您的更改,这可以稍微加快速度。对于本示例,我将使用 Quix IDE。
为了节省时间,我已经为此创建了一些代码,因此您所需要做的就是将其粘贴到相关文件中。
要编辑最大车辆窗口服务:
导航至“应用程序” ,然后单击“最大车辆窗口”以打开 Quix IDE。
请注意,在 Quix 用户界面中,每个服务的代码库被称为“应用程序”,但实际上它是一个独立的文件夹,用于存储特定服务的代码(所有服务共同为计算机视觉应用程序提供支持) 。
如果尚未打开,请单击左侧文件菜单中的main.py
以在 Quix IDE 中将其打开。
在另一个窗口中, 从我们的教程存储库打开此文件,然后复制并粘贴代码,替换所有现有代码。代码注释应该可以帮助您了解我所做的更改。
新代码期望有一个名为“ output2 ”的新环境变量,它存储您之前创建的新输出主题的名称,因此让我们创建该新变量。
现在,您只需保存并部署更改即可。
要保存更改,请单击“提交” 。
在重新部署之前,最好标记修订版本,以便轻松了解部署正在使用的代码版本。
通过单击标签图标来标记提交并为其命名......类似于“NewAggregation”。
如果您想仔细检查新代码是否有效,请单击右上角的“运行” 。
要重新部署服务,请打开右上角的部署下拉列表,然后选择“编辑现有部署”,然后单击“重新部署”。
要检查主题,请打开 Quix 门户,导航到“主题”页面,然后单击您之前创建的“ max-vehicles-agg ”主题。
每个主题都有一个称为“数据浏览器视图”的视图,可让您检查流经主题的消息。
您现在应该在“ SELECT STREAMS ”部分中看到一个活动流。
选择流“ aggregate_data ”(或者任何它被调用的流)
然后在“选择参数...”部分中选择所有可用参数。
最后,选择表视图,以便您可以查看所选的数据。
请注意,新数据可能不会立即通过,因为TFL Camera Feed服务具有可变的睡眠计时器,以避免达到 TfL API 的速率限制。您可以在“sleep_interval”环境变量中配置它。在撰写本文时,默认设置为 60 秒。
如果您检查TFL Camera Feed部署的日志,您应该会看到此计时器何时被激活。当您看到数据再次出现时,可以安全地返回并检查您的主题
如果您担心更改前端代码,可以跳过这部分。 Quix 主要是一个后端工具,但我们添加了一个前端组件,以便您创建一个功能齐全的迷你应用程序。在本部分中,您将更新前端以显示聚合。
现在,让我们更新 UI 服务以包含我们在后端创建的新聚合。提醒一下,完成后它应该是这样的。
它并不漂亮,但它为我们提供了所需的信息。要更新 UI,您需要编辑以下文件:
' app.component.ts
' 管理前端的数据订阅:
' app.component.html
'定义页面上信息的布局。
让我们从app.component.ts
开始。在 Quix 门户中,导航到“应用程序”,然后单击TfL 图像处理 UI (部署为“项目前端”)以打开 Quix IDE。
更新数据订阅
在这里,我们将有点 hacky 并对主题引用进行硬编码。在生产中,这应该使用变量来处理,但这使演示更简单。
在“应用程序文件”部分中,打开./src/app/app.component.ts
。
找到以下块(第 213 行之后):
subscribeToData() { this.connection.invoke('SubscribeToParameter', this._topicName, this._streamId, 'image'); this.connection.invoke('SubscribeToParameter', this._topicName, this._streamId, 'lat'); this.connection.invoke('SubscribeToParameter', this._topicName, this._streamId, 'lon'); this.connection.invoke('SubscribeToParameter', 'max-vehicles', '*', 'max_vehicles'); this.connection.invoke('SubscribeToParameter', 'image-vehicles', '*', '*');
以及块下方的以下额外行:
this.connection.invoke('SubscribeToParameter', 'max-vehicles-agg', '*', '*'); // new line
这将启动对该主题的订阅并读取消息中的所有参数(parameterData 是 Quix API 中的特定数据类型,通常由数字数据组成。)
接下来,找到行'selectedMarker: Marker | undefined;
'(第 43 行或附近)并在其下方添加以下新行。
latestMessageMaxAgg: ParameterData | undefined;
此行初始化一个新变量,您将使用该变量存储消息中的数据。
现在,每当检测到新消息时,我们就将数据分配给变量。
首先,找到以下块(第 108 行之后):
if (data.topicName === "image-vehicles") { key = data.streamId; if (data.numericValues['vehicles']) markerData.count = data.numericValues['vehicles'][0]; if (data.numericValues[this.parameterId]) markerData.value = data.numericValues[this.parameterId][0]; }
在其下方添加以下块:
if (data.topicName === 'max-vehicles-agg') { this.latestMessageMaxAgg = data; }
现在,如果消息来自名为“ max-vehicles-agg
”的主题,前端将获取消息中的所有数据并将其放入变量latestMessageMaxAgg
中。
现在我们已经可以访问该变量了,让我们在前端渲染它的内容。
更新前端模板
现在,是时候最终渲染我们提供给前端的数据了。
./src/app/app.component.html
。
找到以下呈现彩色交通密度比例的 div(第 85 行之后):
<div> <p class="mat-caption text-body mb-1">Traffic density</p>
在其正上方添加以下代码块。
<div *ngIf="latestMessageMaxAgg"> <h4 _ngcontent-kap-c49="" class="mb-2">Combined Maximums Across All London Cameras</h4> <table> <tbody> <tr><td><strong>All vehicles:</strong></td> <td> {{ latestMessageMaxAgg?.numericValues?.['combined_max_vehicles_for_all_cameras']?.at(0) }} </td> </tr> <tr><td><strong>Cars:</strong></td> <td> {{ latestMessageMaxAgg?.numericValues?.['combined_max_cars_for_all_cameras']?.at(0) }} </td> </tr> <tr><td><strong>Buses:</strong></td> <td> {{ latestMessageMaxAgg?.numericValues?.['combined_max_buses_for_all_cameras']?.at(0) }} </td> </tr> <tr><td><strong>Trucks:</strong></td> <td> {{ latestMessageMaxAgg?.numericValues?.['combined_max_trucks_for_all_cameras']?.at(0) }} </td> </tr> <tr><td><strong>Motorcycles:</strong></td> <td> {{ latestMessageMaxAgg?.numericValues?.['combined_max_motorbikes_for_all_cameras']?.at(0) }} </td> </tr> </tbody> </table> </div>
这将从您之前创建的latestMessageMaxAgg
变量中提取数据,并显示最新消息中的数据(通过“ at(0)
”选择)。它还使数据成为可选的,这样您就不会收到数据丢失的错误消息。
如果您想先在本地计算机上测试它,您可以拉取您在 Quix IDE 中所做的更改(Quix 自动推送它们)并按照前端服务 README中的说明进行操作。
要重新部署TfL 图像处理 UI服务,请遵循与重新部署max cars 服务时相同的流程。
如果出现任何问题,请记住,您可能必须删除该服务并重新部署它,然后才能在日志中看到错误输出。
您可能已经注意到,该应用程序实际上并不是在计算一段时间内的车辆数量,而只是计算在任何给定视频帧中观察到的车辆数量。
这是因为我们没有使用 YOLOv8 的全部功能。我们只是使用对象检测,但要正确计算车辆数量,您需要使用对象跟踪。问题是,对象跟踪需要更多内存,而 Quix 免费计划中不提供这些内存。该演示使用最小的“纳米”YOLO 模型,但还有四种其他尺寸可用,其中 YOLOv8x 是最强大的。如果您使用更大的模型,您可以获得很好的车辆跟踪和计数结果。
这是在我的本地计算机(具有不错的 GPU)上尝试通过 TfL 摄像头运行它的屏幕截图。
我将 YOLO 与其他几个库(例如 Roboflow 的监督)结合使用来统计沿道路双向行驶的车辆。
有关如何获得类似结果的更多信息,请参阅以下资源:
使用 YOLOv8 跟踪和计数对象(roboflow.com)
这是关于使用 Jupyter Notebook 使用计算机视觉进行车辆计数的优秀入门读本。
YOLOv8 物体检测和计数 |作者:达斯汀·刘 | 2023 年 9 月 |数据驱动的投资者
Roboflow Notebook 的优化 Streamlit 版本的演练(在之前链接的文章中讨论) - 您也可以在线尝试。
感谢您能走到这一步。我希望您能够成功定制它。如果您有任何问题,请务必在我们的社区论坛中发布问题,我们中的一个人会立即解决。
正如您所看到的,在 Quix 中部署和运行复杂的应用程序相当简单。这些演示被设计为独立的,因此我们也托管前端。然而,在生产场景中,您可能希望在其他地方运行前端。 Quix 真正擅长的是处理事件流并以极其高性能的方式执行复杂的计算。它利用 Apache Kafka 的优势来大规模处理数据,同时消除其一些弱点(例如资源管理和配置复杂性)。当然,如果您已经拥有自己的 Kafka 实例,或者正在使用 Confluence Cloud,您也可以使用它。 Quix 可以帮助您实时编排和处理事件流。
作者: Tomáš Neubauer (Quix 首席技术官兼联合创始人)
也发布在这里。