|
プロジェクトの背景 私の故郷には高齢者が住んでいて、監視カメラを設置しています。普段は思い出した時にしか映像を確認しないため、多くの瞬間を見逃してしまいます。そこで、人手による監視に代わるアルゴリズムを使って、誰かが家にいる瞬間を記録したいと思っています。 プロジェクト紹介 このプロジェクトでは、OrangePi AIPro開発ボードを利用して、ビジョンベースのインテリジェント監視システムを実装します。このボードは、既存のホームネットワークカメラから映像を読み取り、画像に多角形領域を追加することで検出範囲を制限します。次に、検出範囲内の画像に対して人物認識が行われます。OrangePi AIProはAI開発ボードであるため、そのAscend 310 SOCはAIアルゴリズムを使用してこの種の問題を解決するのに適しています。人物検出に選択されたアルゴリズムはYOLOv5で、AIProと完全に互換性があり、NPUの8Tコンピューティングパワーを最大限に活用できます。人物が検出されると、アラーム遅延が計算され、アラームがトリガーされます。Webサービスは、クラウドサーバーまたはAIProのローカルにセットアップされます。検出デバイスが人物を識別すると、POST経由でWebバックエンドにアラーム情報を送信します。Webバックエンドは、データベース記録とアラーム表示のためにアラーム情報を受信します。 ハードウェア構成 AIPro 開発ボードは、次のようなさまざまな高度なハードウェア機能を備えた強力な人工知能開発ボードです。
ソフトウェアの使用 aipro開発ボードは豊富なソフトウェアエコシステムを誇り、PythonやC++などのプログラミング言語をサポートしています。私はプログラミング速度が速いという理由からPythonを選択しました。ファームウェアはデフォルトでPython 3.10に設定されており、Python 3.9用のconda環境を提供しています。PyTorchの公式ドキュメントでは3.9が指定されているため、私はPython 3.9を直接使用しました。aipro開発ボードは、AI推論のためのNPU呼び出しであるPyTorchをサポートしており、推論のためのモデル量子化(OM)モデルもサポートしています。OMモデルは非常に効率的であるため、迷わず選択しました。 ハードウェアの準備
ソフトウェアの準備
アルゴリズムのアイデアと実装プロセス
プロセスのテストとデバッグ 私が持っているAIProはハイエンドの16GB版で、ヒートシンクもケースも付いていません。このHuawei Ascend 310 SoCは非常に多くの熱を発生し、銅フィンのヒートシンクだけでは対応しきれませんでした。5Vファンを見つけるために持ち物を探し回らなければなりませんでした。そのため、購入時には放熱性について必ず考慮する必要があります。 64GBのメモリカードを用意し、公式サイトからイメージをダウンロードして書き込みます。システムを起動するには、適切なDIPスイッチを選択する必要があります。DIPスイッチの組み合わせによって、起動するストレージメディアが異なります。詳細は公式ドキュメントをご覧ください。 システムのフラッシュ後、カードを挿入し、電源プラグを差し込むと自動的に電源が入ります。パスワードを入力するとデスクトップ画面が表示されます。電源は65W PDプロトコルを採用しています。 システムには、関連するAIアルゴリズムライブラリがすべてデフォルトで付属しています。公式デモを実行してご確認ください。公式デモはPythonで実装されており、IDEはJupyterを使用しています。ユーザーディレクトリ内のsamplesフォルダに対応するファイルパスを開き、Jupyterサービスを実行してください。スクリプトディレクトリでターミナルを開き、コマンド`./start_notebook.sh`を使用してサービスを開始してください。実行後の結果は以下の画像に示されています。ターミナルにブラウザのアドレスが表示されます。 Jupyterサービスを開始したら、ブラウザを開いてプロジェクトページにアクセスしてください。Jupyterスクリプトを実行すると、アドレスが表示されます。 左側の 01-yolov5 フォルダを開き、main.ipynb プログラムを実行して、NPU 推論結果を確認します。 詳細についてはビデオをご覧ください: NPU機能をテストした後、プロジェクト内のpyファイルに合わせてipynbファイルを修正し、必要に応じてAPI呼び出しにカプセル化する必要があります。必要なAPIのみを残しました。 画像前処理インターフェースと前処理方法: def preprocess_image(image, cfg, bgr2rgb=True): 画像前処理 img、scale_ratio、pad_size = letterbox(image、new_shape=cfg['input_shape']) bgr2rgbの場合: 画像 = 画像[:, :, ::-1] img = img.transpose(2, 0, 1) # HWC2CHW img = np.ascontiguousarray(img, dtype=np.float32) img、scale_ratio、pad_size を返す def letterbox(img, new_shape=(640, 640), color=(114, 114, 114), auto=False, scaleFill=False, scaleup=True): # 画像を 32 ピクセル倍の長方形にリサイズします https://github.com/ultralytics/yolov3/issues/232 shape = img.shape[:2] # 現在のシェイプ [高さ, 幅] isinstance(new_shape, int)の場合: 新しい形状 = (新しい形状、新しい形状) # スケール比(新旧) r = min(新しい形状[0] / 形状[0], 新しい形状[1] / 形状[1]) if not scaleup: # スケールダウンのみ行い、スケールアップは行わない(より良いテスト mAP のため) r = min(r, 1.0) # パディングを計算する ratio = r, r # 幅と高さの比率 new_unpad = int(round(shape[1] * r)), int(round(shape[0] * r)) dw, dh = new_shape[1] - new_unpad[0], new_shape[0] - new_unpad[1] # whパディング 自動の場合: # 最小の長方形 dw, dh = np.mod(dw, 64), np.mod(dh, 64) # wh パディング elif scaleFill: # ストレッチ dw、dh = 0.0、0.0 new_unpad = (新しいシェイプ[1]、新しいシェイプ[0]) ratio = new_shape[1] / shape[1], new_shape[0] / shape[0] # 幅と高さの比率 dw /= 2 # パディングを2辺に分割する dh /= 2 if shape[::-1] != new_unpad: # サイズ変更 img = cv2.resize(img, new_unpad, 補間=cv2.INTER_LINEAR) 上、下 = int(round(dh - 0.1)), int(round(dh + 0.1)) 左、右 = int(round(dw - 0.1)), int(round(dw + 0.1)) img = cv2.copyMakeBorder(img, top, bottom, left, right, cv2.BORDER_CONSTANT, value=color) # 境界線を追加 認識結果は、img、ratio、(dw、dh) として返され、境界ボックスを表示します。 defdraw_bbox(bbox, img0, color, wt): 画像上に予測ボックスを描画します。 preson = False
enumerate(bbox[:, 5])のidx、class_idについて:
float(bbox[idx][4] < float(0.05))の場合:
続く
img0 = cv2.rectangle(img0, (int(bbox[idx][0]), int(bbox[idx][1])), (int(bbox[idx][2]), int(bbox[idx][3])), color, wt)
img0 = cv2.putText(img0, config.class_names[int(class_id)] + ' {:.4f}'.format(bbox[idx][4]),
(int(bbox[idx][0]), int(bbox[idx][1] - 10)), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2)
int(class_id) == 0 の場合:
プレゼンス = 真実
img0、preson を返す結果の後処理 def def_infer_frame_with_vis(image_o, image): グローバルモデル、cfg # データ前処理 img、scale_ratio、pad_size = preprocess_image(image、cfg、True) # モデル推論 出力 = model.infer([画像])[0] 出力 = torch.tensor(出力) # 非最大抑制の後処理: boxout = nms(output, conf_thres=cfg["conf_thres"], iou_thres=cfg["iou_thres"]) pred_all = ボックスアウト[0].numpy() # 座標変換を予測する scale_coords(cfg['input_shape'], pred_all[:, :4], image.shape, ratio_pad=(scale_ratio, pad_size)) # 画像予測結果を視覚化する img_vis, preson = draw_bbox(pred_all, image_o, (0, 255, 0), 2) _ = 0 len(pred_all) > 0 かつ preson の場合: _ = 1 img_vis, _ を返す 次に、監視カメラを設置します。Hikvisionのカメラを使用していますが、RTSPにアクセスできるカメラであれば何でも構いません。 カメラとAIProを同じルーターに接続し、OpenCVでRTSP読み込みのテストコードを記述し、正常に動画がキャプチャできるかテストします。 cv2をインポート
rtsp_url = "rtsp://ユーザー名:パスワード@ip_address:ポート/ストリーム"
キャップ = cv2.VideoCapture(rtsp_url)
真の場合:
ret、フレーム = cap.read()
そうでない場合:
print("エラー: フレームを読み取ることができません。")
壊す
cv2.imshow('RTSP ストリーム', フレーム)
cv2.waitKey(1) & 0xFF == ord('q')の場合:
壊す
キャップ.リリース()
cv2.すべてのウィンドウを破棄する()動画の読み込み後、NPU推論機能を追加し、RTSP上で推論性能をリアルタイムでモニタリングする必要があります。以下の動画では、NPUが非常に高いリアルタイム性能で人物とボトルを認識している様子が確認できます。(動画2はリモート画面の録画、動画3はスマートフォンで録画した動画です。録画速度は実際の認識速度を反映しています。そのため、AIProの真の速度を示すため、以降の動画はスマートフォンで録画しています。) 詳細についてはビデオ2と3をご覧ください。 CPU使用率を確認すると、全体のCPU使用率は20%未満です。理論上、NPUが動作しているときはCPUがフル負荷になることはありません。これは、主要な推論計算がNPUで行われるためです。これが、AI開発ボードとしてのAiProの意義です。推論タスク中のCPU使用率が高くなるのは、ビデオデコード、画像の前処理、出力結果の後処理といったタスクがCPUでしか実行できず、NPUは推論段階のみを処理するためです。 詳細についてはビデオ4をご覧ください。 ビデオ読み取りとリアルタイム推論の問題を解決した後、次のステップはビデオ線描画機能(電子フェンスとも呼ばれます)を追加することです。まず、線描画スキームのコードを以下に示します。コードは非常にシンプルです。クラスを作成し、マウスイベントをOpenCVシリアルポートにバインドします。マウスをクリックすると点が記録され、`self.marks`に保存されます。このプロセスは継続的に繰り返されます。ボタンイベントが定義されており、'1' を押すとすべての点が保存され、適用されます。 クラスマーク:
def __init__(self, marks, dpoint):
自己マーク = []
self.dpoint = False
def draw_circle(self, イベント, x, y, フラグ, pam):
イベント == cv2.EVENT_LBUTTONDBLCLK の場合:
自己マーク追加([int(x), int(y)])
self.dpoint = True
def keyn(self, camcfg):
open ('points_cfg/cfg%s.pkl'%camcfg,'wb') を f として実行します:
pickle.dump(self.marks, f)
self.points_config = np.array(self.marks, dtype=np.int32)
自己マーク = []
self.dpoint = False
MARK = mark([], False)マウスで複数のポイントをクリックし、1 を押すと閉じて表示されます。 詳細についてはビデオ5をご覧ください。 描画フレームが完成すると、描画ウィンドウの外側のグラフィックはフィルタリングされ、認識されなくなります。 len(MARK.points_config) > 0の場合:
ポリゴン = np.zeros(frame.shape, np.uint8)
ポリゴン = cv2.polylines(Polygon, [MARK.points_config], True, (0, 0, 0))
ポリゴン = cv2.fillPoly(ポリゴン, [MARK.points_config], (255, 255, 255))
result_img = cv2.bitwise_and(ポリゴン、フレーム)
#cv2.imshow("showre", result_img)
それ以外:
result_img = フレーム表示効果としては、描画領域外のターゲットは認識されません。 AIPro は結果を識別し、それを郵送でプッシュします。 def sent(pic, ID, url, label):
pic=base64.b64encode(cv2.imencode('.jpg',pic)[1]).decode()
データ = {"alarmDev":ID,"alarmImg":pic,"label":label}
json_mod = json.dumps(データ)
ヘッダー = {"Content-Type":"application/json;charset=UTF-8"}
res = リクエスト.post(url=url,data=json_mod,headers=ヘッダー)
print("httpサーバーにメッセージを投稿: ", res.content.decode("utf-8"))サーバーに Django バックエンドを設定し、データベースを作成し、データベース内にアラーム時間、アラームの種類、アラーム画像のパスを記録する 2 つのテーブルを作成します。 最近のアラーム情報を表示する簡単なページを作成します。 <!DOCTYPE html>
<html>
<ヘッド>
<メタ文字セット="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="ビューポート" content="width=デバイス幅、初期スケール=1.0">
<title>最新の5枚の写真のみ</title>
</head>
<本文>
<a href="http://192.168.50.100:8003/show_pic"><input type="button" value='手動更新'></a>
データベース内の画像の合計数: {{sum_len}} 枚。
</br>
<img src={{pic1}} 幅='355px' 高さ='200px'>
</br>
<strong>{{name1}} </strong></br>
</br>
<img src={{pic2}} 幅='355px' 高さ='200px'>
</br>
<strong>{{name2}} </strong></br>
</br>
<img src={{pic3}} 幅='355px' 高さ='200px'>
</br>
<strong>{{name3}} </strong></br>
</br>
<img src={{pic4}} 幅='355px' 高さ='200px'>
</br>
<strong>{{name4}} </strong></br>
</br>
<img src={{pic5}} 幅='355px' 高さ='200px'>
</br>
<strong>{{name5}} </strong></br>
</br>
</br>
</br>
<a href="http://192.168.50.100:8003/admin"> Django </a> による
</br>
</br>
</本文>
</html>AIProはローカルエリアネットワーク(LAN)上に設置されています。LAN上のAIProは監視フィードを読み取り、アラーム画像をクラウドサーバーにプッシュします。ブラウザを使ってクラウドサーバーのアドレスにアクセスすることで、アラーム画像を閲覧できます。 クラウドプラットフォーム上のリアルタイム視聴アドレス: http://118.89.106.119:8001/show_pic |