ショッピングモールやスーパーマーケットなどの交通量が密な場面では、エスカレーターで歩行者の転倒や怪我が頻繁に報告されていますが、AI技術の急速な発展と普及に伴い、ショッピングモールやスーパーマーケット、地下鉄などでも歩行者が転倒して怪我をする場面が増えています。専用の安全検知および早期警告システムを設置する 中核的な動作原理は、AI モデルとカメラ画像およびビデオ ストリームのリアルタイム計算です 行動エスカレーター上の行動のリアルタイム検出と識別を通じて、危険な行為に対する警告と対応は、その後の重大な結果を回避するために実行できます。
本稿の主な目的は、スーパーマーケットのエスカレーターシーンを基にした歩行者の安全行動検知・認識システムを開発・構築し、AI技術を活用した安全確保向上の実現可能性を探索・分析することである。例:
ターゲット検出モデル SSD (Single Shot Multibox Detector) は、単一の順伝播でターゲットの位置とカテゴリを同時に予測できるエンドツーエンドのターゲット検出モデルです。 SSD は、回帰のアイデアとアンカー ボックス メカニズムを組み合わせ、2 段階アルゴリズムにおける候補領域の生成とその後のピクセルまたは特徴のリサンプリング段階を排除し、すべての計算をネットワーク内にカプセル化することで、トレーニングを容易にし、高速化します。これは、境界ボックスの出力空間を一連のデフォルト境界ボックスに離散化します。これらの境界ボックスは、さまざまなレベルの特徴マップで生成され、さまざまなアスペクト比を持ちます。予測時に、ネットワークは、ターゲットをしっかりと囲むように、各デフォルト境界ボックス内の各カテゴリに属する可能性を予測します。ネットワークは、解像度が異なる複数の特徴マップで予測を行い、さまざまなサイズのオブジェクトを処理できます。
SSD ネットワークは、特徴抽出と検出フレーム生成の 2 つの部分に分けることができます。特徴抽出に使用される基本ネットワークは、分類ネットワークから借用されます。 SSD は基本ネットワーク構造として VGG-16 を使用し、VGG-16 の最初の 5 層を使用し、FC6 層と FC7 層を 2 つの畳み込み層に変換します。このモデルには、さらに 3 つの畳み込み層と平均プーリング層が追加されます。ただし、この変更により受容野のサイズが変化するため、拡張コンボリューションが使用されます。畳み込み層は、プルーニングされたベース ネットワークの後に追加され、これらの層の特徴マップ サイズは、複数のスケールでの予測を可能にするために徐々に縮小されます。マルチスケール特徴マップには、conv4-3、conv7、conv8-2、conv9-2、conv10-2、conv11-2 の合計 6 つのスケールが含まれます。 SSD は、追加された各フィーチャ レイヤーで小さなコンボリューション カーネルを使用して、一連の境界ボックス オフセットを予測します。予測部分は、オブジェクト カテゴリの信頼度を予測し、特徴マップ上で小さいサイズのコンボリューション カーネルを使用してオブジェクトの境界ボックス座標を直接予測するために使用されます。これは、予測が 6 つの異なるスケールで実行され、各スケールにアンカーがあるためです。異なるアスペクト比のフレームを使用すると、ターゲット検出の精度が向上し、アルゴリズム全体をエンドツーエンドでトレーニングできるため、検出速度にも大きな利点があります。
SSD のアルゴリズム構築原理は次のとおりです。
特徴の抽出: SSD はまず、VGG や ResNet などの畳み込みニューラル ネットワーク (CNN) を使用して、入力画像の特徴を抽出します。これらの特徴マップには、さまざまなレベルのセマンティック情報が含まれており、モデルがさまざまなサイズやカテゴリのターゲットを検出するのに役立ちます。
マルチスケール検出: SSD は、さまざまなレベルの特徴マップに一連の畳み込み層とプーリング層を適用して、さまざまなスケールでオブジェクトを検出します。このマルチスケール検出により、モデルはさまざまなサイズのオブジェクトに適切に適応できるようになります。
境界ボックスとカテゴリを予測する: 各機能マップで、SSD は畳み込みニューラル ネットワークを使用して、境界ボックスとオブジェクト カテゴリの位置を予測します。アンカー ボックスの位置とサイズごとに、SSD は一致するターゲット境界ボックスと対応するクラス確率を予測します。
マッチング戦略: SSD は、予測されたバウンディング ボックスとグラウンド トゥルース ターゲット バウンディング ボックスの間の IoU (交差オーバーユニオン) をマッチングすることによってどの予測が有効であるかを判断し、最適化に損失関数を使用します。
SSD の利点は次のとおりです。
効率: SSD は 1 回の順伝播でターゲット検出を完了でき、より高速です。
マルチスケール検出: SSD は、さまざまなサイズのターゲットを効果的に検出し、マルチスケールのターゲット検出のニーズに適応できます。
シンプルかつ直接的: SSD は単一のモデルを使用して検出を完了し、モデルの複雑さを簡素化します。
SSD の欠点は次のとおりです。
位置決め精度の低下: 小さなターゲットの位置決めでは、SSD の精度が制限される場合があります。
ターゲットの除外の問題: SSD にプリセットされているアンカー ボックスにより、複数の検出結果の間で相互排他が発生する可能性があり、これを解決するには追加の処理が必要です。
論文のアドレスはこちらです。
詳細については論文をご覧ください。
官方项目地址在这里,如下所示:
このプロジェクトでは、使用する 3 つの異なるバックボーン ネットワークが提供されます。ここでは、以下に示すように mobilenetv3 を使用します。
"""
Creates a MobileNetV3 Model as defined in:
Andrew Howard, Mark Sandler, Grace Chu, Liang-Chieh Chen, Bo Chen, Mingxing Tan, Weijun Wang, Yukun Zhu, Ruoming Pang, Vijay Vasudevan, Quoc V. Le, Hartwig Adam. (2019).
Searching for MobileNetV3
arXiv preprint arXiv:1905.02244.
@ Credit from https://github.com/d-li14/mobilenetv3.pytorch
@ Modified by Chakkrit Termritthikun (https://github.com/chakkritte)
"""
import torch.nn as nn
import math
from ssd.modeling import registry
from ssd.utils.model_zoo import load_state_dict_from_url
model_urls = {
'mobilenet_v3': 'https://github.com/d-li14/mobilenetv3.pytorch/raw/master/pretrained/mobilenetv3-large-1cd25616.pth',
}
def _make_divisible(v, divisor, min_value=None):
"""
This function is taken from the original tf repo.
It ensures that all layers have a channel number that is divisible by 8
It can be seen here:
https://github.com/tensorflow/models/blob/master/research/slim/nets/mobilenet/mobilenet.py
:param v:
:param divisor:
:param min_value:
:return:
"""
if min_value is None:
min_value = divisor
new_v = max(min_value, int(v + divisor / 2) // divisor * divisor)
# Make sure that round down does not go down by more than 10%.
if new_v < 0.9 * v:
new_v += divisor
return new_v
class h_sigmoid(nn.Module):
def __init__(self, inplace=True):
super(h_sigmoid, self).__init__()
self.relu = nn.ReLU6(inplace=inplace)
def forward(self, x):
return self.relu(x + 3) / 6
class h_swish(nn.Module):
def __init__(self, inplace=True):
super(h_swish, self).__init__()
self.sigmoid = h_sigmoid(inplace=inplace)
def forward(self, x):
return x * self.sigmoid(x)
class SELayer(nn.Module):
def __init__(self, channel, reduction=4):
super(SELayer, self).__init__()
self.avg_pool = nn.AdaptiveAvgPool2d(1)
self.fc = nn.Sequential(
nn.Linear(channel, _make_divisible(channel // reduction, 8)),
nn.ReLU(inplace=True),
nn.Linear(_make_divisible(channel // reduction, 8), channel),
h_sigmoid()
)
def forward(self, x):
b, c, _, _ = x.size()
y = self.avg_pool(x).view(b, c)
y = self.fc(y).view(b, c, 1, 1)
return x * y
def conv_3x3_bn(inp, oup, stride):
return nn.Sequential(
nn.Conv2d(inp, oup, 3, stride, 1, bias=False),
nn.BatchNorm2d(oup),
h_swish()
)
def conv_1x1_bn(inp, oup):
return nn.Sequential(
nn.Conv2d(inp, oup, 1, 1, 0, bias=False),
nn.BatchNorm2d(oup),
h_swish()
)
class InvertedResidual(nn.Module):
def __init__(self, inp, hidden_dim, oup, kernel_size, stride, use_se, use_hs):
super(InvertedResidual, self).__init__()
assert stride in [1, 2]
self.identity = stride == 1 and inp == oup
if inp == hidden_dim:
self.conv = nn.Sequential(
# dw
nn.Conv2d(hidden_dim, hidden_dim, kernel_size, stride, (kernel_size - 1) // 2, groups=hidden_dim, bias=False),
nn.BatchNorm2d(hidden_dim),
h_swish() if use_hs else nn.ReLU(inplace=True),
# Squeeze-and-Excite
SELayer(hidden_dim) if use_se else nn.Identity(),
# pw-linear
nn.Conv2d(hidden_dim, oup, 1, 1, 0, bias=False),
nn.BatchNorm2d(oup),
)
else:
self.conv = nn.Sequential(
# pw
nn.Conv2d(inp, hidden_dim, 1, 1, 0, bias=False),
nn.BatchNorm2d(hidden_dim),
h_swish() if use_hs else nn.ReLU(inplace=True),
# dw
nn.Conv2d(hidden_dim, hidden_dim, kernel_size, stride, (kernel_size - 1) // 2, groups=hidden_dim, bias=False),
nn.BatchNorm2d(hidden_dim),
# Squeeze-and-Excite
SELayer(hidden_dim) if use_se else nn.Identity(),
h_swish() if use_hs else nn.ReLU(inplace=True),
# pw-linear
nn.Conv2d(hidden_dim, oup, 1, 1, 0, bias=False),
nn.BatchNorm2d(oup),
)
def forward(self, x):
if self.identity:
return x + self.conv(x)
else:
return self.conv(x)
class MobileNetV3(nn.Module):
def __init__(self, mode='large', num_classes=1000, width_mult=1.):
super(MobileNetV3, self).__init__()
# setting of inverted residual blocks
self.cfgs = [
# k, t, c, SE, HS, s
[3, 1, 16, 0, 0, 1],
[3, 4, 24, 0, 0, 2],
[3, 3, 24, 0, 0, 1],
[5, 3, 40, 1, 0, 2],
[5, 3, 40, 1, 0, 1],
[5, 3, 40, 1, 0, 1],
[3, 6, 80, 0, 1, 2],
[3, 2.5, 80, 0, 1, 1],
[3, 2.3, 80, 0, 1, 1],
[3, 2.3, 80, 0, 1, 1],
[3, 6, 112, 1, 1, 1],
[3, 6, 112, 1, 1, 1],
[5, 6, 160, 1, 1, 2],
[5, 6, 160, 1, 1, 1],
[5, 6, 160, 1, 1, 1]]
assert mode in ['large', 'small']
# building first layer
input_channel = _make_divisible(16 * width_mult, 8)
layers = [conv_3x3_bn(3, input_channel, 2)]
# building inverted residual blocks
block = InvertedResidual
for k, t, c, use_se, use_hs, s in self.cfgs:
output_channel = _make_divisible(c * width_mult, 8)
exp_size = _make_divisible(input_channel * t, 8)
layers.append(block(input_channel, exp_size, output_channel, k, s, use_se, use_hs))
input_channel = output_channel
# building last several layers
layers.append(conv_1x1_bn(input_channel, exp_size))
self.features = nn.Sequential(*layers)
self.extras = nn.ModuleList([
InvertedResidual(960, _make_divisible(960 * 0.2, 8), 512, 3, 2, True, True),
InvertedResidual(512, _make_divisible(512 * 0.25, 8), 256, 3, 2, True, True),
InvertedResidual(256, _make_divisible(256 * 0.5, 8), 256, 3, 2, True, True),
InvertedResidual(256, _make_divisible(256 * 0.25, 8), 64, 3, 2, True, True),
])
self.reset_parameters()
def forward(self, x):
features = []
for i in range(13):
x = self.features[i](x)
features.append(x)
for i in range(13, len(self.features)):
x = self.features[i](x)
features.append(x)
for i in range(len(self.extras)):
x = self.extras[i](x)
features.append(x)
return tuple(features)
def reset_parameters(self):
for m in self.modules():
if isinstance(m, nn.Conv2d):
n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels
m.weight.data.normal_(0, math.sqrt(2. / n))
if m.bias is not None:
m.bias.data.zero_()
elif isinstance(m, nn.BatchNorm2d):
m.weight.data.fill_(1)
m.bias.data.zero_()
elif isinstance(m, nn.Linear):
n = m.weight.size(1)
m.weight.data.normal_(0, 0.01)
m.bias.data.zero_()
@registry.BACKBONES.register('mobilenet_v3')
def mobilenet_v3(cfg, pretrained=True):
model = MobileNetV3()
if pretrained:
model.load_state_dict(load_state_dict_from_url(model_urls['mobilenet_v3']), strict=False)
return model
README に従って、独自のデータセットに基づいてモデル開発プロセスを実装します。ここでは詳細については説明しません。前の記事に詳しい紹介があります。
トレーニングが完了すると、推論に使用できる重みファイルが取得されます。視覚的な推論の例は次のとおりです。
次のようにフォーマットされてここに保存されます。
{
"shake": [
[
0.6235582828521729,
[
409,
837,
1019,
1054
]
]
]
}
その後のバックエンド業務システムの分析や利用に便利なので、興味のある方はぜひ実践してみてください!