店舗でのショールーミング対策において、効果が未知数の段階で高額なAIカメラシステムへ大規模投資を行うことは、経営的にも技術的にも非効率です。まずは手元のツールでプロトタイプを作成し、「実際にどう動くか」をスピーディーに仮説検証することが、ビジネスへの最短距離を描く鍵となります。
最新の物体検知モデル「YOLOv8」とPythonを活用すれば、ショールーミング検知の基本ロジックを驚くほど少ないコード量で実装できます。
本記事では、外部ベンダーへ依頼する前に、自社のエンジニアリソースで「ショールーミング検知システム」をDIYする方法を解説します。実際にコードを動かしてデータ取得の感覚を掴むことで、将来的な本格導入の成功確率を飛躍的に高めましょう。
1. ショールーミング行動を「コード」で定義する
人間の曖昧な行動を、プログラムが理解できる明確なロジックに変換します。「ショールーミング」は「実店舗で商品を確認し、その場で購入せずECサイトで購入する行動」を指しますが、カメラ映像だけで「ECで購入したか」まで追跡することは不可能です。
そこで、この行動を観測可能な物理現象として再定義してみましょう。
ショールーミングのプロキシ(代替)指標定義:
特定の商品棚エリア(ROI)に一定時間以上滞留し、かつその間にスマートフォンを操作している状態
技術的アプローチ:Object Detection vs Pose Estimation
骨格検知(Pose Estimation)は「スマホを見ている姿勢」を細かく分析できますが、計算コストが高く、リアルタイム処理にはハイスペックなGPUが必要です。
今回は「PoCの迅速さ」と「実用的な速度」を優先し、物体検知(Object Detection)を採用します。YOLOv8で「人(Person)」と「携帯電話(Cell phone)」を検出し、位置関係と時間経過を分析するアプローチであれば、一般的なPCでも十分に動作します。まずは動くものを作ることが重要です。
システムの全体像
今回実装するパイプラインは以下の通りです。
- Input: 店舗カメラの映像(または動画ファイル)
- Process:
- YOLOv8で物体検知&トラッキング
- 指定エリア(ROI)内での滞留判定
- 人物とスマホの重なり判定(スマホ操作検知)
- Output: 検知ログ(CSV)および検知画面の描画
2. 開発環境のセットアップと依存ライブラリ
さっそく開発環境を構築しましょう。Python 3.8以上のインストールを前提とします。
必要なライブラリは、画像処理のOpenCV、データ操作のPandas、Ultralyticsが提供するYOLOv8です。
# 仮想環境の作成をお勧めします
python -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
# 必要なライブラリのインストール
pip install ultralytics opencv-python pandas
モデルの選択
YOLOv8にはサイズごとに複数のモデル(n, s, m, l, x)が存在します。PoC段階では推論速度を最優先し、Nanoモデル(yolov8n.pt)を使用します。精度が不足する場合のみ、SmallやMediumへスケールアップするのが、アジャイルな開発の基本です。
初回実行時にモデルファイルが自動ダウンロードされますが、手動での用意も可能です。
3. 実装Step 1:人物トラッキングと滞留時間の計測
店舗内で「誰が」「どこに」「どれくらい居るか」を正確に把握するためのベースコードを作成します。
YOLOにはBoT-SORTやByteTrackといった強力なトラッキング機能が標準統合されており、model.track()メソッドを呼び出すだけで、フレームを跨いで同一人物に一貫したIDを付与できます。
なお、最新のYOLOアーキテクチャでは推論速度向上とエッジデバイス最適化が優先され、従来モデルのNMS(Non-Maximum Suppression)やDFL(Distribution Focal Loss)が廃止される傾向にあります。本番環境やエッジデバイスへのデプロイ時には、後処理不要なOne-to-One Headの使用が新たに推奨されているため、最新版移行時は公式ドキュメントで最新仕様と代替手段を確認してください。技術スタックのアップデートは常に意識しておきたいポイントです。
今回のプロトタイプ開発では、以下のベースコードでトラッキングと滞留時間計測のコアロジックを検証します。
import cv2
import numpy as np
from ultralytics import YOLO
from collections import defaultdict
# モデルの読み込み(初回は自動ダウンロードされます)
model = YOLO('yolov8n.pt')
# 動画ファイルのパス(0に設定すればWebカメラのリアルタイム映像を使用可能)
video_path = 'shop_sample.mp4'
cap = cv2.VideoCapture(video_path)
# トラッキング履歴と滞留開始時間を保持するための辞書を初期化
track_history = defaultdict(lambda: [])
dwell_time = defaultdict(lambda: 0)
entry_time = {}
# ROI(関心領域:商品棚の前など)の定義 [x, y]
# 実際のカメラ映像の画角に合わせて、対象エリアの座標を調整してください
roi_points = np.array([[100, 100], [500, 100], [500, 400], [100, 400]], np.int32)
while cap.isOpened():
success, frame = cap.read()
if not success:
break
# YOLOによるトラッキングの実行
# persist=Trueでフレーム間のIDを維持し、classes=[0]で「Person(人物)」のみを検知対象とします
results = model.track(frame, persist=True, classes=[0], verbose=False)
if results[0].boxes.id is not None:
boxes = results[0].boxes.xywh.cpu()
track_ids = results[0].boxes.id.int().cpu().tolist()
for box, track_id in zip(boxes, track_ids):
x, y, w, h = box
# 人物の足元の座標(x, y + h/2)を基準にしてエリア判定を行うのが一般的です
foot_point = (int(x), int(y + h / 2))
# ROI判定: 戻り値が正ならエリア内部、負なら外部、0は境界線上を示します
in_roi = cv2.pointPolygonTest(roi_points, foot_point, False) >= 0
if in_roi:
# エリア内に入った初回時刻を記録
if track_id not in entry_time:
entry_time[track_id] = cap.get(cv2.CAP_PROP_POS_MSEC) / 1000.0
# 現在の滞留時間を計算(秒単位)
current_time = cap.get(cv2.CAP_PROP_POS_MSEC) / 1000.0
duration = current_time - entry_time[track_id]
dwell_time[track_id] = duration
# 画面上への描画処理(可視化)
cv2.rectangle(frame, (int(x-w/2), int(y-h/2)), (int(x+w/2), int(y+h/2)), (0, 255, 0), 2)
cv2.putText(frame, f"ID:{track_id} {duration:.1f}s", (int(x-w/2), int(y-h/2)-10),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)
else:
# エリア外に出た場合は記録をリセット(要件に応じて保持するよう変更も可能)
if track_id in entry_time:
del entry_time[track_id]
# ROIエリア(対象となる商品棚の前など)を青枠で描画
cv2.polylines(frame, [roi_points], True, (255, 0, 0), 2)
# 処理結果の映像を表示
cv2.imshow("YOLO Tracking", frame)
# 'q'キーが押されたらループを抜ける
if cv2.waitKey(1) & 0xFF == ord("q"):
break
cap.release()
cv2.destroyAllWindows()
コードを実行すると、緑色のバウンディングボックスで囲まれた人物が青枠のエリア(ROI)に進入した瞬間から、秒数がカウントアップされる様子を画面上で確認できます。
ここでのポイントは、人物全体ではなく「足元の座標」を基準にエリア判定を行っている点です。頭や胴体の中心座標を使うと、カメラの設置角度によってはエリア外に立っていてもエリア内と誤判定されるケースが増加します。足元の座標を基準にすることで、物理的な立ち位置に近い正確な滞留判定が可能となります。現場の物理的な制約を考慮した、実践的なアプローチと言えるでしょう。
4. 実装Step 2:スマホ操作の検知と行動判定
次に、ショールーミングの重要要素である「スマホ操作」を検知します。COCOデータセット(YOLOの学習データ)には cell phone(クラスID: 67)が含まれています。
「人物の枠(BBox)の中に、スマホの枠が含まれている(または重なっている)」 場合に、「その人がスマホを操作している」と判定するロジックを採用します。
先ほどのコードを拡張し、クラスID 0(人)と 67(携帯電話)の両方を検知するよう変更してみましょう。
# ...(前略: import等は共通)
# クラスID定義
CLASS_PERSON = 0
CLASS_PHONE = 67
while cap.isOpened():
success, frame = cap.read()
if not success: break
# PersonとCell phoneの両方を検知
results = model.track(frame, persist=True, classes=[CLASS_PERSON, CLASS_PHONE], verbose=False)
# 結果の整理
persons = []
phones = []
if results[0].boxes.id is not None:
boxes = results[0].boxes.xyxy.cpu().numpy() # xyxy形式(左上、右下座標)
class_ids = results[0].boxes.cls.cpu().numpy()
track_ids = results[0].boxes.id.int().cpu().numpy()
for box, cls, tid in zip(boxes, class_ids, track_ids):
if cls == CLASS_PERSON:
persons.append({'box': box, 'id': tid})
elif cls == CLASS_PHONE:
phones.append({'box': box})
# スマホ所持判定ロジック
showrooming_ids = []
for p in persons:
p_box = p['box'] # [x1, y1, x2, y2]
has_phone = False
for ph in phones:
ph_box = ph['box']
# 簡易的な包含判定: スマホの中心が人のBBox内にあるか
ph_center_x = (ph_box[0] + ph_box[2]) / 2
ph_center_y = (ph_box[1] + ph_box[3]) / 2
if (p_box[0] < ph_center_x < p_box[2]) and (p_box[1] < ph_center_y < p_box[3]):
has_phone = True
break
# ROI判定と滞留時間計算(前述のロジックと同様)
# ここでは簡略化のため省略しますが、in_roi判定と組み合わせます
# 判定結果の描画
color = (0, 0, 255) if has_phone else (0, 255, 0) # スマホありなら赤
label = f"ID:{p['id']} Phone:{'Yes' if has_phone else 'No'}"
cv2.rectangle(frame, (int(p_box[0]), int(p_box[1])), (int(p_box[2]), int(p_box[3])), color, 2)
cv2.putText(frame, label, (int(p_box[0]), int(p_box[1])-10),
cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 2)
cv2.imshow("Showrooming Detection", frame)
if cv2.waitKey(1) & 0xFF == ord("q"):
break
# ...(後略)
このコードでは、人物の枠内にスマホの中心点がある場合、「スマホ所持(Phone: Yes)」として枠を赤色に変更します。これに「滞留時間 > 30秒」などの条件をANDで組み合わせれば、立派なショールーミング検知器として機能します。複雑なAIモデルを組まなくても、シンプルなロジックの組み合わせでビジネス課題にアプローチできる好例です。
5. データ出力と分析:検知ログの可視化
検知結果を画面表示するだけでなく、データを蓄積して分析可能にすることで、初めてビジネス価値が生まれます。
検知イベント発生時に、以下のようなCSV形式でログを出力する機能の追加を推奨します。
timestamp, person_id, dwell_time, has_phone, roi_id
2023-10-27 14:30:05, 102, 45.2, True, shelf_A
2023-10-27 14:32:10, 105, 12.0, False, shelf_B
データ分析のヒント:
蓄積されたCSVデータをPandasで読み込めば、次のような分析が可能です。
- エリア別ショールーミング率: 特定の商品棚だけスマホ利用率が高い場合、その商品は「他店やネットと比較されやすい」可能性があります。価格競争力が低いか、スペック比較が必要な商品かもしれません。
- 時間帯別傾向: 土日の午後にショールーミングが増えるなら、その時間帯に詳しいスタッフを配置して接客でクロージングする、といった対策が可能です。
import pandas as pd
# ログデータの読み込み
df = pd.read_csv('detection_log.csv')
# スマホ利用あり、かつ30秒以上滞留したユーザーを抽出
showroomers = df[(df['has_phone'] == True) & (df['dwell_time'] > 30)]
# エリアごとの発生件数を集計
print(showroomers['roi_id'].value_counts())
6. 実運用に向けた課題とプライバシー配慮
PoCで技術的な手応えを得た後、実店舗への本格展開において最重要となる課題が「プライバシー」です。店舗内で顧客の顔や姿を撮影し、そのままデータを保存することは、法的・倫理的に重大なリスクを伴います。
システム設計の初期段階からプライバシー保護を組み込む「プライバシー・バイ・デザイン」の思想が不可欠です。行動検知の処理プロセスには生の画像データが必要ですが、分析後のデータ保存やモニタリング画面への表示時には、確実な匿名化処理を施す設計が求められます。
顔の匿名化(モザイク処理)の実装例
匿名化のアプローチとして、物体検知モデルで顔領域を特定する方法や、処理負荷軽減のために人物バウンディングボックスの上部一定割合(例:1/5程度)を顔領域とみなして簡易的なぼかし処理を適用する手法があります。
以下は、検知された人物領域の上部20%を顔領域と仮定し、ガウシアンブラーを適用するPython実装例です。
def apply_blur(image, box):
x1, y1, x2, y2 = map(int, box)
# 人物の上部20%を顔領域と仮定(簡易版)
face_h = int((y2 - y1) * 0.2)
face_roi = image[y1:y1+face_h, x1:x2]
# ガウシアンブラーでぼかし
blurred_face = cv2.GaussianBlur(face_roi, (99, 99), 30)
image[y1:y1+face_h, x1:x2] = blurred_face
return image
このような処理をパイプラインの最終段に組み込むことで、個人を特定可能な情報が永続的なストレージに保存されるリスクを大幅に軽減できます。
エッジデバイスへのデプロイとハードウェア選定
実店舗環境に高スペックなPCを常設することはスペースや管理コストの面で非現実的なため、エッジデバイスへのデプロイが一般的です。要求される処理能力と導入コストのバランスを見極めた選定が重要となります。
1. NVIDIA Jetsonシリーズ(高性能エッジAI向け)
本格的なリアルタイム分析には、現行のNVIDIA Jetsonモジュールが推奨されます。最新アーキテクチャ採用モデルは、前世代と比較してエネルギー効率と演算性能が飛躍的に向上しています。強化されたメモリ帯域を活かし、人物検知と姿勢推定など複数AIモデルの同時稼働にも対応可能です。店舗内で遅延のない行動分析を行う場合、このクラスの処理能力がシステム安定稼働の基盤となります。
2. Raspberry Pi 5(コスト重視・軽量タスク向け)
導入コストを最小限に抑えたい場合や設置スペースが限られる小規模店舗では、Raspberry Pi 5も有力な選択肢です。ただし、専用AIアクセラレータを持たないため推論速度に物理的な制約が伴います。軽量モデル(Nanoサイズなど)の選択、入力解像度の低下、検知フレームレートの意図的な引き下げなど、ソフトウェア側での細やかなチューニングが不可欠です。
3. 推論の高速化(TensorRT)
ハードウェア性能を限界まで引き出すには、ソフトウェアレイヤーでの最適化が必須です。モデルをTensorRT形式へエクスポートすることで、NVIDIA GPU搭載デバイス上での推論レイテンシを大幅に改善できます。エッジ環境の限られたリソースを有効活用するため、FP16やINT8へのモデル量子化、TensorRTによるグラフ最適化は標準的なアプローチです。TensorRTの機能や推奨手順は継続的にアップデートされるため、実装時はNVIDIAの公式ドキュメントで最新情報を確認してください。
まとめ:まずは小さく、早く試そう
「ショールーミング検知」という複雑なビジネス課題も、技術要素に分解すれば「特定エリアでの滞留検知」と「物体検知」の組み合わせとして捉えられます。最初から高額な専用システムを導入する前に、まずはプロトタイプによるPoCを実施し、実際の店舗環境で取得できるデータを検証することが効果的です。
「現在のカメラ構成で意図した行動を検知できるのか」「取得データからどのような顧客インサイトが見出せるのか」
このような仮説検証サイクルを小規模かつスピーディーに回すことが、実用的なDX推進の第一歩となります。自作プロトタイプで有用性を確認し、より高度な分析や全国規模での安定した運用インフラが必要になった段階で、要件に合致した専門プラットフォームの導入を検討することが、経営的にも合理的です。
まずは手元の環境でモデルを動かし、現場の生データから得られる知見を確認してみましょう。技術の本質を見抜き、ビジネスへの最短距離を描くための第一歩です。
コメント