TFLite と OpenVINO を使用したセルフィーのセグメント化#
この Jupyter ノートブックはオンラインで起動でき、ブラウザーのウィンドウで対話型環境を開きます。ローカルにインストールすることもできます。次のオプションのいずれかを選択します:
セルフィーセグメント化パイプラインを使用すると、開発者はシーン内の背景とユーザーを簡単に分離し、重要な部分に集中できます。セルフィーにクールなエフェクトを追加したり、ユーザーを興味深い背景環境に挿入したりすることが、これまでになく簡単になりました。写真編集以外にも、この技術はビデオ会議にも重要です。ビデオ通話中に背景をぼかしたり置き換えたりするのに役立ちます。
このチュートリアルでは、OpenVINO を使用してセルフィーセグメント化を実装する方法について説明します。Google MediaPipe ソリューションの一部として提供されるマルチクラス・セルフィー・セグメント化モデルを使用します。
マルチクラス・セルフィー・セグメント化モデルは、マルチクラス・セマンティック・セグメント化モデルであり、各ピクセルを背景、髪、体、顔、衣服、その他 (アクセサリーなど) に分類します。このモデルは、フレーム内の 1 人または複数の人物、自撮り写真、全身画像をサポートします。このモデルは、リアルタイムのパフォーマンスを実現するためにカスタマイズされたボトルネックとデコーダー・アーキテクチャーを備えた Vision Transformer に基づいています。モデルの詳細については、モデルカードをご覧ください。このモデルは Tensorflow Lite 形式で表されます。TensorFlow Lite (TFLite とも呼ばれます) は、マシン・ラーニング・モデルをエッジデバイスにデプロイするために開発されたオープンソース・ライブラリーです。
このチュートリアルは次のステップで構成されます:
TFLite モデルをダウンロードし、OpenVINO IR 形式に変換します。
画像に対して推論を実行します。
ビデオでインタラクティブな背景ぼかしのデモを実行します。
目次:
必要条件#
必要な依存関係をインストールします#
import platform
%pip install -q "openvino>=2023.1.0" opencv-python tqdm
if platform.system() != "Windows":
%pip install -q "matplotlib>=3.4"
else:
%pip install -q "matplotlib>=3.4,<3.7"
Note: you may need to restart the kernel to use updated packages.
Note: you may need to restart the kernel to use updated packages.
import requests
r = requests.get(
url="https://raw.githubusercontent.com/openvinotoolkit/openvino_notebooks/latest/utils/notebook_utils.py",
)
open("notebook_utils.py", "w").write(r.text)
23215
事前学習済みモデルとテスト画像をダウンロード#
from pathlib import Path
from notebook_utils import download_file
tflite_model_path = Path("selfie_multiclass_256x256.tflite")
tflite_model_url = "https://storage.googleapis.com/mediapipe-models/image_segmenter/selfie_multiclass_256x256/float32/latest/selfie_multiclass_256x256.tflite"
download_file(tflite_model_url, tflite_model_path)
selfie_multiclass_256x256.tflite: 0%| | 0.00/15.6M [00:00<?, ?B/s]
PosixPath('/opt/home/k8sworker/ci-ai/cibuilds/ov-notebook/OVNotebookOps-727/.workspace/scm/ov-notebook/notebooks/tflite-selfie-segmentation/selfie_multiclass_256x256.tflite')
Tensorflow Lite モデルを OpenVINO IR 形式に変換#
2023.0.0 リリース以降、OpenVINO は TFLite モデル変換をサポートします。ただし、TFLite モデル形式は read_model
で直接渡すことができます (TFLite でのこの API の使用例は、TFLite から OpenVINO への変換チュートリアルと基本 OpenVINO API 機能のチュートリアルにあります)。追加の最適化 (例: 重みを FP16 形式に圧縮する) を適用するには、モデルを OpenVINO 中間表現形式に変換することを推奨します。TFLite モデルを OpenVINO IR に変換するには、モデル変換 Python API を使用できます。ov.convert_model
関数は、TFLite モデルへのパスを受け取り、このモデルを表す OpenVINO Model クラスのインスタンスを返します。取得したモデルはすぐに使用でき、compile_model
を使用してデバイスにロードするか、ov.save_model
関数を使用してディスクに保存して次回の実行時のロード時間を短縮できます。モデル変換の詳細については、このページを参照してください。TensorFlow Lite については、モデルのサポートを参照してください。
import openvino as ov
core = ov.Core()
ir_model_path = tflite_model_path.with_suffix(".xml")
if not ir_model_path.exists():
ov_model = ov.convert_model(tflite_model_path)
ov.save_model(ov_model, ir_model_path)
else:
ov_model = core.read_model(ir_model_path)
print(f"Model input info: {ov_model.inputs}")
Model input info: [<Output: names[input_29] shape[1,256,256,3] type: f32>]
モデル入力は、N, H, W, C
形式の [1、256、256、3] 形状の浮動小数点テンソルです。ここで:
N
- バッチサイズ、入力画像の数。H
- 入力画像の高さ。W
- 入力画像の幅。C
- 入力画像のチャネル。
このモデルは、255 で割って [0, 1] の範囲に正規化された RGB 形式の画像を受け入れます。
print(f"Model output info: {ov_model.outputs}")
Model output info: [<Output: names[Identity] shape[1,256,256,6] type: f32>]
モデル出力は、チャネル数 (背景、髪、体の皮膚、顔の皮膚、衣服など、サポートされているセグメント化クラスの数を表す 6) を除いて、同様の形式と形状の浮動小数点テンソルです。出力テンソルの各値は、ピクセルが指定されたクラスに属する確率を表します。argmax
演算を使用すると、各ピクセルの最も高い確率を持つラベルを取得できます。
画像に対して OpenVINO モデル推論を実行#
実際のモデルを見てみましょう。OpenVINO で推論モデルを実行するには、まずデバイスにモデルをロードする必要があります。選択する推論デバイスについては、次のドロップダウン・リストを使用してください。
モデルのロード#
import ipywidgets as widgets
device = widgets.Dropdown(
options=core.available_devices + ["AUTO"],
value="AUTO",
description="Device:",
disabled=False,
)
device
Dropdown(description='Device:', index=1, options=('CPU', 'AUTO'), value='AUTO')
compiled_model = core.compile_model(ov_model, device.value)
入力画像を準備#
モデルはサイズが 256x256 の画像を受け入れるため、入力画像のサイズを変更してモデルの入力テンソルに収まるようにする必要があります。通常、セグメント化モデルは入力画像の詳細の比率に敏感なので、元のアスペクト比を維持し、パディングを追加するとセグメント化の精度が向上します。これには、前処理アプローチを使用します。さらに、入力画像は UINT8 ([0, 255] データ範囲) の RGB 画像として表されるため、[0, 1] に正規化する必要があります。
import cv2
import numpy as np
from notebook_utils import load_image
# 入力画像を読み取り RGB に変換
test_image_url = "https://user-images.githubusercontent.com/29454499/251036317-551a2399-303e-4a4a-a7d6-d7ce973e05c5.png"
img = load_image(test_image_url)
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
# 前処理ヘルパー関数
def resize_and_pad(image: np.ndarray, height: int = 256, width: int = 256):
"""
Input preprocessing function, takes input image in np.ndarray format,
resizes it to fit specified height and width with preserving aspect ratio
and adds padding on bottom or right side to complete target height x width rectangle.
Parameters:
image (np.ndarray): input image in np.ndarray format
height (int, *optional*, 256): target height
width (int, *optional*, 256): target width
Returns:
padded_img (np.ndarray): processed image
padding_info (Tuple[int, int]): information about padding size, required for postprocessing
"""
h, w = image.shape[:2]
if h < w:
img = cv2.resize(image, (width, np.floor(h / (w / width)).astype(int)))
else:
img = cv2.resize(image, (np.floor(w / (h / height)).astype(int), height))
r_h, r_w = img.shape[:2]
right_padding = width - r_w
bottom_padding = height - r_h
padded_img = cv2.copyMakeBorder(img, 0, bottom_padding, 0, right_padding, cv2.BORDER_CONSTANT)
return padded_img, (bottom_padding, right_padding)
# 前処理ステップを適用 - 入力画像のサイズ変更とパディング
padded_img, pad_info = resize_and_pad(np.array(img))
# 入力データを uint8 [0, 255] から float32 [0, 1] の範囲に変換し、バッチ次元を追加
normalized_img = np.expand_dims(padded_img.astype(np.float32) / 255, 0)
モデルの推論を実行#
out = compiled_model(normalized_img)[0]
推論結果を後処理して視覚化#
モデルはサイズが 256 x 256 のセグメント化確率マスクを予測します。各ピクセルに対して最も高い確率のラベルを取得し、結果を元の入力画像サイズで復元するには、後処理を適用する必要があります。モデルの結果はさまざまな方法で解釈できます。例えば、セグメント化マスクを視覚化したり、選択した背景 (削除、他の画像との置き換え、ぼかし) や他のクラス (人物の髪の色の変更や化粧の追加など) に視覚効果を適用できます。
from typing import Tuple
from notebook_utils import segmentation_map_to_image, SegmentationMap, Label
# 視覚化のセグメント化ラベルのヘルパー
labels = [
Label(index=0, color=(192, 192, 192), name="background"),
Label(index=1, color=(128, 0, 0), name="hair"),
Label(index=2, color=(255, 229, 204), name="body skin"),
Label(index=3, color=(255, 204, 204), name="face skin"),
Label(index=4, color=(0, 0, 128), name="clothes"),
Label(index=5, color=(128, 0, 128), name="others"),
]
SegmentationLabels = SegmentationMap(labels)
# 後処理出力マスクのヘルパー
def postprocess_mask(out: np.ndarray, pad_info: Tuple[int, int], orig_img_size: Tuple[int, int]):
"""
Posptprocessing function for segmentation mask, accepts model output tensor, gets labels for each pixel using argmax,
unpads segmentation mask and resizes it to original image size.
Parameters:
out (np.ndarray): model output tensor
pad_info (Tuple[int, int]): information about padding size from preprocessing step
orig_img_size (Tuple[int, int]): original image height and width for resizing
Returns:
label_mask_resized (np.ndarray): postprocessed segmentation label mask
"""
label_mask = np.argmax(out, -1)[0]
pad_h, pad_w = pad_info
unpad_h = label_mask.shape[0] - pad_h
unpad_w = label_mask.shape[1] - pad_w
label_mask_unpadded = label_mask[:unpad_h, :unpad_w]
orig_h, orig_w = orig_img_size
label_mask_resized = cv2.resize(label_mask_unpadded, (orig_w, orig_h), interpolation=cv2.INTER_NEAREST)
return label_mask_resized
# オリジナル画像に関する情報を取得
image_data = np.array(img)
orig_img_shape = image_data.shape
# 置換する背景色を指定
BG_COLOR = (192, 192, 192)
# ガウスぼかしをによる背景ぼかしシナリオの画像をぼかす
blurred_image = cv2.GaussianBlur(image_data, (55, 55), 0)
# 後処理出力
postprocessed_mask = postprocess_mask(out, pad_info, orig_img_shape[:2])
# 色分けされたセグメント化マップを取得
output_mask = segmentation_map_to_image(postprocessed_mask,
SegmentationLabels.get_colormap())
# 元の画像の背景を置き換え、
# 単色の背景色で画像を塗りつぶす
bg_image = np.full(orig_img_shape, BG_COLOR, dtype=np.uint8)
# 背景と前景を分離するための条件マスクを定義
condition = np.stack((postprocessed_mask,) * 3, axis=-1) > 0
# 背景を単色に置き換え
output_image = np.where(condition, image_data, bg_image)
# 背景をぼかした画像のコピーに置き換え
output_blurred_image = np.where(condition, image_data, blurred_image)
取得した結果を視覚化
import matplotlib.pyplot as plt
titles = ["Original image", "Portrait mask", "Removed background", "Blurred background"]
images = [image_data, output_mask, output_image, output_blurred_image]
figsize = (16, 16)
fig, axs = plt.subplots(2, 2, figsize=figsize, sharex="all", sharey="all")
fig.patch.set_facecolor("white")
list_axes = list(axs.flat)
for i, a in enumerate(list_axes):
a.set_xticklabels([])
a.set_yticklabels([])
a.get_xaxis().set_visible(False)
a.get_yaxis().set_visible(False)
a.grid(False)
a.imshow(images[i].astype(np.uint8))
a.set_title(titles[i])
fig.subplots_adjust(wspace=0.0, hspace=-0.8)
fig.tight_layout()

インタラクティブな背景ぼかしのデモ動画#
次のコードは、ビデオに対してモデル推論を実行します:
import collections
import time
from IPython import display
from typing import Union
from notebook_utils import VideoPlayer
# 背景ぼかしを実行するメイン処理関数
def run_background_blurring(
source: Union[str, int] = 0,
flip: bool = False,
use_popup: bool = False,
skip_first_frames: int = 0,
model: ov.Model = ov_model,
device: str = "CPU",
):
"""
Function for running background blurring inference on video
Parameters:
source (Union[str, int], *optional*, 0): input video source, it can be path or link on video file or web camera id.
flip (bool, *optional*, False): flip output video, used for front-camera video processing
use_popup (bool, *optional*, False): use popup window for avoid flickering
skip_first_frames (int, *optional*, 0): specified number of frames will be skipped in video processing
model (ov.Model): OpenVINO model for inference
device (str): inference device
Returns:
None
"""
player = None
compiled_model = core.compile_model(model, device)
try:
# ターゲット fps で再生するビデオプレーヤーを作成
player = VideoPlayer(source=source, flip=flip, fps=30, skip_first_frames=skip_first_frames)
# キャプチャーを開始
player.start()
if use_popup:
title = "Press ESC to Exit"
cv2.namedWindow(winname=title, flags=cv2.WINDOW_GUI_NORMAL | cv2.WINDOW_AUTOSIZE)
processing_times = collections.deque()
while True:
# フレームをグラブ
frame = player.next()
if frame is None:
print("Source ended")
break
# フレームがフル HD より大きい場合は、サイズを縮小してパフォーマンスを向上させます
scale = 1280 / max(frame.shape)
if scale < 1:
frame = cv2.resize(
src=frame,
dsize=None,
fx=scale,
fy=scale,
interpolation=cv2.INTER_AREA,
)
# 結果を取得
input_image, pad_info = resize_and_pad(frame, 256, 256)
normalized_img = np.expand_dims(input_image.astype(np.float32) / 255, 0)
start_time = time.time()
# モデルは RGB 画像を期待しますが、ビデオは BGR でキャプチャーします
segmentation_mask = compiled_model(normalized_img[:, :, :, ::-1])[0]
stop_time = time.time()
blurred_image = cv2.GaussianBlur(frame, (55, 55), 0)
postprocessed_mask = postprocess_mask(segmentation_mask, pad_info, frame.shape[:2])
condition = np.stack((postprocessed_mask,) * 3, axis=-1) > 0
frame = np.where(condition, frame, blurred_image)
processing_times.append(stop_time - start_time)
# 最後の 200 フレームの処理時間を使用
if len(processing_times) > 200:
processing_times.popleft()
_, f_width = frame.shape[:2]
# 平均処理時間 [ms]
processing_time = np.mean(processing_times) * 1000
fps = 1000 / processing_time
cv2.putText(
img=frame,
text=f"Inference time: {processing_time:.1f}ms ({fps:.1f} FPS)",
org=(20, 40),
fontFace=cv2.FONT_HERSHEY_COMPLEX,
fontScale=f_width / 1000,
color=(255, 0, 0),
thickness=1,
lineType=cv2.LINE_AA,
)
# ちらつきがある場合はこの回避策を使用
if use_popup:
cv2.imshow(winname=title, mat=frame)
key = cv2.waitKey(1)
# escape = 27
if key == 27:
break
else:
# numpy 配列を jpg にエンコード
_, encoded_img = cv2.imencode(ext=".jpg", img=frame, params=[cv2.IMWRITE_JPEG_QUALITY, 100])
# IPython イメージを作成
i = display.Image(data=encoded_img)
# このノートブックに画像を表示
display.clear_output(wait=True)
display.display(i)
# ctrl-c
except KeyboardInterrupt:
print("Interrupted")
# 異なるエラー
except RuntimeError as e:
print(e)
finally:
if player is not None: # キャプチャーを停止
Player.stop().stop()
if use_popup:
cv2.destroyAllWindows()
ライブ背景ぼかしを実行#
Web カメラをビデオ入力として使用します。デフォルトでは、プライマリー・ウェブ・カメラは source=0
に設定されます。複数のウェブカメラがある場合、0 から始まる連続した番号が割り当てられます。前面カメラを使用する場合は、flip=True
を設定します。一部のウェブブラウザー、特に Mozilla Firefox ではちらつきが発生する場合があります。ちらつきが発生する場合、use_popup=True
を設定してください。
注: このノートブックをウェブカメラで使用するには、ウェブカメラを備えたコンピューター上でノートブックを実行する必要があります。ノートブックをリモートサーバー (例えば、Binder または Google Colab サービス) で実行する場合、ウェブカメラは動作しません。デフォルトでは、下のセルはビデオファイルに対してモデル推論を実行します。ウェブカメラのセットでライブ推論を試してみたい場合は、
WEBCAM_INFERENCE = True
に設定します
WEBCAM_INFERENCE = False
if WEBCAM_INFERENCE:
VIDEO_SOURCE = 0 # ウェブカメラ
else:
VIDEO_SOURCE =
"https://storage.openvinotoolkit.org/repositories/openvino_notebooks/data/data/video/CEO%20Pat%20Gelsinger%20on%20Leading%20Intel.mp4"
推論するデバイスを選択:
device
Dropdown(description='Device:', index=1, options=('CPU', 'AUTO'), value='AUTO')
実行:
run_background_blurring(source=VIDEO_SOURCE, device=device.value)

ソースの終わり