IP カメラは継続的なビデオストリームを生成するため、大規模な映像データに対する手動レビューは現実的ではありません。本チュートリアルでは、OSS のデータインデックス機能を活用して、自然言語による説明(例:「駐車中の車のある庭」)で映像ライブラリを検索できる意味検索システムを構築する方法を説明します。
ソリューションの概要
本ソリューションは以下の 2 つのステップで構成されます:
バケットの作成と動画のアップロード:IP カメラから取得した生の動画ファイルを OSS バケットに格納します。
AISearch 機能の有効化:対象バケットに対して AISearch を有効化し、OSS が動画コンテンツをインデックス化して、自然言語による検索を可能にします。
主な機能
| 機能 | 説明 |
|---|---|
| 意味検索 | 自然言語による説明および複数条件の組み合わせで動画を検索できます。手動レビューを行わずに、特定のフレームを迅速に特定できます。 |
| マルチモーダル検索 | 単一のインターフェイスから動画、画像、テキストを横断的に検索できます。技術的ハードルおよび運用・保守(O&M)コストを低減します。 |
| 水平方向のスケーリング | OSS は、大規模かつ増加傾向にある動画データセットに対応可能な、無制限の容量と優れた拡張性を提供します。 |
前提条件
開始する前に、以下の条件を満たしていることを確認してください。
OSS へのアクセス権限を持つ Alibaba Cloud アカウント
OSS コンソールへのアクセス権限
アップロード準備が完了した動画ファイル(ステップ 1 でサンプルファイルを提供)
ステップ 1:バケットの作成と動画のアップロード
OSS コンソールにログインします。
バケットページに移動し、バケットの作成をクリックします。
バケットの作成ページで、ビジネス内容に関連付けたバケット名(例:
ipc-videos-oss-metaquery-demo)を入力します。その他のパラメーターはすべてデフォルト設定のままとします。作成をクリックします。バケットが作成された後、バケットの表示をクリックします。
[ファイル一覧] ページで、[ファイルをアップロード] をクリックし、[ファイルをスキャン] を選択します。アップロードする動画ファイル(例:[Video A.mp4]、[Video B.mp4]、[Video C.mp4])を選択します。デフォルト設定をそのままにして、[ファイルをアップロード] をクリックします。
(任意)アップロード済みの動画ファイルにタグを追加します。タグを付与することで、インデックス作成および検索時にカメラ ID や所在地などの属性で動画をフィルターできます。操作列の対象ファイルに対して、
> その他 > タグ付けを選択します。ダイアログボックスで、タグのキーと値のペアを追加します(例: OK をクリックします)。インデックス作成時のフィルター条件として使用する場合、キーを
need-seek、値をtrueに設定します。検索時にカメラごとに結果をフィルターする場合、キーを
camera、値をcamera-aに設定します。
ステップ 2:AISearch 機能の有効化
左側ナビゲーションウィンドウで、ファイル管理 > データインデックス を選択します。
データインデックスページで、初めてデータインデックスを利用する場合は、画面上の指示に従って AliyunMetaQueryDefaultRole ロールに必要な権限を付与します。これにより、OSS がご利用のバケット内のデータを管理できるようになります。権限付与後、データインデックスの有効化 をクリックし、AISearch を選択します。
(任意)AI コンテンツ認識:ニーズに応じて、画像コンテンツ認識 または 動画コンテンツ認識 を選択します。
(任意)ファイルフィルタリングルール:特定の条件に一致するファイルのみを対象に AI 分析を実行するように設定できます。最大 5 つのルールを設定可能です。フィルター基準には、プレフィックス、ファイルサイズ、タグ、最終更新日時(LastModifiedTime)が含まれます。たとえば、キー
need-seek、値trueのタグフィルタリングルールを追加すると、該当タグが付与されたファイルのみがインデックス化されます。ファイルフィルタリングが有効化されている場合、データインデックス、AISearch、およびコンテンツ認識の課金対象は、フィルター適用後のファイル数のみとなります。
有効化 をクリックします。
メタデータインデックスの構築には時間がかかります。所要時間はバケット内のオブジェクト数によって異なります。ステータスを確認するには、ページを更新してください。
![]() | ![]() |
|---|
結果の検証
単に説明的なテキスト(例: a yard with a parked car)を入力するだけで、システムが説明に一致する関連する動画を返します。
バケットページで、ご利用のバケット名をクリックします。
ファイルページで、動画が正常にアップロードされたことを確認します。
左側ナビゲーションウィンドウで、ファイル管理 > データインデックス を選択します。
データインデックスページの検索ボックスに、
a yard with a parked carを入力します。メディアタイプ では、Videoを選択します。(任意) オブジェクトタグ付け で、キーを
camera、値をcamera-aに設定します。この設定により、camera=camera-aのタグが付与された動画のみが返されます。検索 をクリックします。
検索結果からファイルパスをコピーします。その後、オブジェクトページに戻り、検索ボックスにパスを貼り付けて検索することで、該当する動画を表示できます。
![]() |
|---|
本番環境への適用
上記のコンソール操作は、機能の検証を目的としています。本番環境では、OSS のソフトウェア開発キット(SDK)を活用して、動画のアップロード、タグ管理、検索クエリを自動化してください。
OSS への動画アップロード
IP カメラは継続的なビデオストリームを生成します。OSS Python SDK を使用して、動画セグメントをバケットに自動的にアップロードします。
以下の例では、ファイルアップロードマネージャーを使用して動画をアップロードしています:
import argparse
import alibabacloud_oss_v2 as oss
from alibabacloud_oss_v2.models import ListObjectsRequest, PutObjectTaggingRequest, Tagging, TagSet, Tag, PutObjectRequest
import os
def upload_video(client, bucket, video_config):
"""動画のアップロード"""
try:
# ファイルの存在確認
if not os.path.exists(video_config['path']):
print(f'ファイルが存在しません: {video_config["path"]}')
return False
# アップロード対象のオブジェクトを作成
uploader = client.uploader()
# アップロードリクエストの実行 - filepath パラメーターを正しく指定
result = uploader.upload_file(
filepath=video_config['path'], # filepath パラメーターを追加
request=PutObjectRequest(
bucket=bucket,
key=video_config['key']
)
)
print(f'{video_config["key"]} のアップロードが成功しました:')
print(f'状態コード: {result.status_code},'
f' リクエスト ID: {result.request_id}')
return True
except Exception as e:
print(f'{video_config["key"]} のアップロードに失敗しました: {str(e)}')
return False
def main():
# OSS 構成
args = argparse.Namespace(
region='cn-beijing',
bucket='ipc-videos-oss-metaquery-demo',
endpoint='https://oss-cn-beijing.aliyuncs.com'
)
# OSS クライアントの構成
credentials_provider = oss.credentials.EnvironmentVariableCredentialsProvider()
cfg = oss.config.load_default()
cfg.credentials_provider = credentials_provider
cfg.region = args.region
cfg.endpoint = args.endpoint
client = oss.Client(cfg)
# 動画ファイルの構成(異なるタグを含む)
videos = [
{
'path': '<Your-Path>/VideoA.mp4',
'key': 'VideoA.mp4'
},
{
'path': '<Your-Path>/VideoB.mp4',
'key': 'VideoB.mp4'
},
{
'path': '<Your-Path>/VideoC.mp4',
'key': 'VideoC.mp4'
}
]
# 全ての動画をアップロード
for video in videos:
print(f"\n{video['key']} のアップロードを開始します")
upload_video(
client=client,
bucket=args.bucket,
video_config=video
)
if __name__ == "__main__":
main()実行前にプレースホルダーの値を置き換えてください。
| プレースホルダー | 説明 | 例 |
|---|---|---|
<Your-Path> | 動画ファイルが格納されているローカルディレクトリ | /home/user/videos |
意味検索の実行
OSS MetaQuery をバックエンドサービスに統合し、プログラムから動画インデックスを照会します。すべての検索リクエストは、DoMetaQuery API を使用し、mode=semantic を指定します。
以下の例では、OSS MetaQuery の仕様に準拠した XML リクエストを構築し、署名付き URL を含む一致結果を返します:
# -*- coding: utf-8 -*-
import argparse
import alibabacloud_oss_v2 as oss
# XML 応答の解析
import xml.etree.ElementTree as ET
import json
from datetime import datetime
def get_search_conditions():
"""ユーザーからの複数条件入力の取得"""
print("意味的な説明を入力してください(例:駐車中の車のある庭)")
query = input("> 意味キーワード: ").strip()
while not query:
print("意味キーワードは空にできません!")
query = input("> 意味キーワード: ").strip()
return query
def build_metaquery_xml(query):
"""OSS 仕様に準拠した MetaQuery XML の構築"""
xml_parts = [f'<Query>{query}</Query>']
# MediaTypes タグを追加(ここではハードコーディングで video を指定)
xml_parts.append('<MediaTypes><MediaType>video</MediaType></MediaTypes>')
meta_query_xml = f'''<MetaQuery>
{"".join(xml_parts)}
</MetaQuery>'''
return meta_query_xml # UTF-8 バイトストリームとしてエンコード
def format_result(key, pre_url):
"""単一の検索結果の整形"""
return f""" ファイルパス: {key}
ファイルアドレス: {pre_url}
-----------------------"""
def semantic_search():
# コマンドライン引数に直接値を割り当て
args = argparse.Namespace(
region = 'cn-beijing', # ご利用のリージョンに置き換え
bucket = 'ipc-videos-oss-metaquery-demo', # ご利用のバケット名に置き換え
endpoint = 'https://oss-cn-beijing.aliyuncs.com', # ご利用のエンドポイントに置き換え。不要な場合は空欄または削除可。
)
# OSS クライアントの初期化
credentials = oss.credentials.EnvironmentVariableCredentialsProvider()
cfg = oss.config.load_default()
cfg.credentials_provider = credentials
cfg.region = args.region
if args.endpoint:
cfg.endpoint = args.endpoint
client = oss.Client(cfg)
# ユーザー入力の取得
query = get_search_conditions() # クエリのみ取得
# リクエストの構築
try:
# リクエストボディ(XML データ)の構築
data_str = build_metaquery_xml(query)
# 操作入力の定義
req = oss.OperationInput(
op_name='DoMetaQuery', # カスタム操作名
method='POST', # HTTP メソッド
parameters={ # クエリパラメーター
'metaQuery': '',
'mode': 'semantic',
'comp': 'query',
},
headers=None, # カスタムリクエストヘッダー(任意)
body=data_str.encode("utf-8"), # リクエストボディ(UTF-8 バイトストリームとしてエンコード)
bucket=args.bucket, # 対象バケット名
)
# 汎用インターフェイスを呼び出して操作を実行
resp = client.invoke_operation(req)
except oss.exceptions.ServiceError as e:
print(f" サーバーエラー: {e.message}")
return
root = ET.fromstring(resp.http_response.content.decode('utf-8'))
# すべての File 要素を検索
files = root.findall('.//File')
print(f"\n {len(files)} 件の一致結果が見つかりました:")
for i, file in enumerate(files, 1):
print(f"\nファイル {i}:")
# さまざまなプロパティを取得して表示
uri_element = file.find('URI')
uri = uri_element.text if uri_element is not None else 'N/A'
print(f" URI: {uri}")
key_element = file.find('Filename')
key = key_element.text if key_element is not None else 'N/A'
print(f" ファイル名: {key}")
size_element = file.find('Size')
size = size_element.text if size_element is not None else 'N/A'
print(f" サイズ: {size}")
modified_time_element = file.find('FileModifiedTime')
modified_time = modified_time_element.text if modified_time_element is not None else 'N/A'
print(f" 更新日時: {modified_time}")
content_type_element = file.find('ContentType')
content_type = content_type_element.text if content_type_element is not None else 'N/A'
print(f" ContentType: {content_type}")
media_type_element = file.find('MediaType')
media_type = media_type_element.text if media_type_element is not None else 'N/A'
print(f" MediaType: {media_type}")
# その他のプロパティ(ImageHeight、ImageWidth、OSSStorageClass など)も必要に応じて取得・表示可能
# image_height_element = file.find('ImageHeight')
# image_height = image_height_element.text if image_height_element is not None else 'N/A'
# print(f" ImageHeight: {image_height}")
# 署名付き URL の生成(必要に応じて)
if key != 'N/A': # ファイル名が存在する場合のみ URL を生成
try:
pre_url = client.presign(
oss.GetObjectRequest(
bucket=args.bucket, # バケット名を指定
key=key, # オブジェクトキーを指定
)
)
print(f" ファイルアドレス(署名付き URL): {pre_url.url}")
except Exception as e:
print(f" 署名付き URL の生成に失敗しました: {e}")
print("-" * 20) # 区切り線
if __name__ == "__main__":
semantic_search()プログラムを実行した後、a yard with a parked car などの説明を入力します。システムはデータ インデックスをクエリし、一致する結果を返します。各結果の署名付き URL を使用して、ビデオに直接アクセスします。
出力例:
1 件の一致結果が見つかりました:
ファイル 1:
URI: oss://ipc-videos-oss-metaquery-demo/VideoA.mp4
ファイル名: VideoA.mp4
サイズ: 2311252
更新日時: 2025-05-23T17:38:10+08:00
ContentType: video/mp4
MediaType: video
ファイルアドレス(署名付き URL): https://ipc-videos-oss-metaquery-demo.oss-cn-beijing.aliyuncs.com/VideoA.mp4?x-oss-signature-version=OSS4-HMAC-SHA256&x-oss-date=20250523T094511Z&x-oss-expires=900&x-oss-credential=LTAI********************%2F20250523%2Fcn-beijing%2Foss%2Faliyun_v4_request&x-oss-signature=0bf38092c42a179ff0e8334c8bea3fd92f5a78599038e816e2ed3e02755542af
--------------------動画タグの管理
大規模な動画データセットを扱う際、ファイルパスだけではフィルタリングや分類に十分でないことが多くあります。OSS のオブジェクトタグ付け機能では、ファイルにキーと値のペア形式のメタデータをアタッチできます(例:どのカメラで撮影されたか、どの地理的ゾーンに属するか)。タグを活用することで、以下のような操作が可能になります。
キーと値のペアを用いて動画を動的に分類できます
カメラ ID や所在地など、特定のサブセットに検索結果を迅速に絞り込めます
タグが付与されたファイルにのみ適用されるインデックス作成ルールを設定できます
本チュートリアルで使用する 3 つのサンプル動画のタグ付け内容は以下のとおりです。
| ファイル | 内容 | タグ |
|---|---|---|
| VideoA.mp4 | 裏庭の映像 | camera=camera-a |
| VideoB.mp4 | 販売エリアの映像 | camera=camera-b |
| VideoC.mp4 | 裏庭の映像(VideoA と類似) | camera=camera-c |
| VideoA.mp4 | VideoB.mp4 | VideoC.mp4 |
|---|---|---|
![]() | ![]() | ![]() |
| 裏庭のビデオ。camera-a | 自動販売機のビデオ。camera-b | 裏庭のビデオ。内容は Video A に類似しており、camera-c |
タグは、アップロード時に設定することも、アップロード後に追加・更新することもできます。
タグフィルターによる検索
意味検索では、説明に一致するすべての動画が返されます。結果をさらに絞り込むには(例:特定のカメラで撮影された裏庭の映像のみを取得)、初期検索結果が返された後にタグフィルターを適用します。
以下の例では、意味検索とクライアント側でのタグフィルターを組み合わせています:
# -*- coding: utf-8 -*-
import argparse
import alibabacloud_oss_v2 as oss
# XML 応答の解析
import xml.etree.ElementTree as ET
import json
from datetime import datetime
# --- 新規インポート ---
from alibabacloud_oss_v2.models import GetObjectTaggingRequest
# --- インポート終了 ---
def get_inputs():
"""ユーザーの意味クエリ(必須)およびフィルター条件(任意)の取得"""
# 1. 必須の意味クエリの取得
print(" 意味的な説明を入力してください(例:駐車中の車のある庭)")
query = input("> 意味キーワード: ").strip()
while not query:
print(" 意味キーワードは空にできません!")
query = input("> 意味キーワード: ").strip()
conditions = [] # フィルター条件のリストを初期化
target_tag = None # --- ターゲットタグを格納するために使用 ---
# 2. 任意のタグフィルターの取得
print("\n (任意)タグフィルター条件を入力してください(クライアント側フィルターに使用。例:camera=camera-a。スキップする場合は Enter を押してください)")
tag_input = input("> タグ(key=value 形式): ").strip()
if tag_input:
if '=' in tag_input:
# conditions.append({
# 'field': 'Tags',
# 'value': tag_input,
# 'op': 'eq'
# })
target_tag = tag_input
print(f" 情報:結果取得後に、'{target_tag}' タグを用いたクライアント側フィルターが適用されます。")
else:
print(" タグの形式が正しくありません。'key=value' 形式で入力してください。")
# --- target_tag を返す ---
return query, conditions, target_tag
def build_metaquery_xml(query, conditions):
"""OSS 仕様に準拠した MetaQuery XML の構築(意味クエリおよび任意のフィルターを含む)"""
# 常に意味クエリ部分を含める
xml_parts = [f'<Query>{query}</Query>']
# メディアタイプの制約を追加
xml_parts.append('<MediaTypes><MediaType>video</MediaType></MediaTypes>')
# 任意のフィルター条件を追加 - 意味モードでは一部のフィールドのみサポート(ここでは Filename を例示)
for cond in conditions:
# 意味モードでは Tags フィールドの SimpleQuery は構築しない(現在は conditions に Tags を追加していないが、将来的なために残す)
if cond['field'] == 'Tags':
continue
json_query = json.dumps({
"Field": cond['field'],
"Value": cond['value'],
"Operation": cond['op']
}, ensure_ascii=False)
xml_parts.append(f'<SimpleQuery>{json_query}</SimpleQuery>')
# 完全な MetaQuery XML に結合
meta_query_xml = f'''<MetaQuery>
{"".join(xml_parts)}
</MetaQuery>'''
return meta_query_xml
def format_result(key, pre_url):
"""単一の検索結果の整形"""
return f""" ファイルアドレス: {pre_url}
ファイルパス: {key}
-----------------------"""
def perform_search():
# コマンドライン引数に直接値を割り当て
args = argparse.Namespace(
region='cn-beijing', # ご利用のリージョンに置き換え
bucket='ipc-videos-oss-metaquery-demo', # ご利用のバケット名に置き換え
endpoint='https://oss-cn-beijing.aliyuncs.com', # ご利用のエンドポイントに置き換え
)
# OSS クライアントの初期化
credentials = oss.credentials.EnvironmentVariableCredentialsProvider()
cfg = oss.config.load_default()
cfg.credentials_provider = credentials
cfg.region = args.region
if args.endpoint:
cfg.endpoint = args.endpoint
client = oss.Client(cfg)
# ユーザー入力(意味クエリ + 任意の条件 + ターゲットタグ)の取得
query, conditions, target_tag = get_inputs()
# リクエストの構築
try:
# リクエストボディ(XML データ)の構築
# --- Tags を含まない conditions のみを渡す ---
data_str = build_metaquery_xml(query, conditions)
# 操作入力の定義
req = oss.OperationInput(
op_name='DoMetaQuery',
method='POST',
parameters={
'metaQuery': '',
'mode': 'semantic', # <-- 意味モードを再指定
'comp': 'query',
},
headers=None,
body=data_str.encode("utf-8"),
bucket=args.bucket,
)
# 汎用インターフェイスを呼び出して操作を実行
print("\n DoMetaQuery リクエストを送信中...")
resp = client.invoke_operation(req)
print(f"\n リクエストが成功しました。HTTP ステータスコード: {resp.http_response.status_code}")
except oss.exceptions.ServiceError as e:
print(f" サーバーエラー(ServiceError): {e.message}")
print(f" - HTTP ステータスコード: {e.status_code}")
print(f" - エラーコード: {e.error_code}")
print(f" - リクエスト ID: {e.request_id}")
return
except oss.exceptions.ClientError as e: # クライアントエラーまたはネットワークエラーのキャッチ
print(f" クライアントまたはネットワークエラー(ClientError): {e.message}")
return
except Exception as e: # その他の可能性のある例外をキャッチ
print(f" 予期しないエラー: {e}")
import traceback
traceback.print_exc() # トレースバックを完全に出力
return
# 結果の解析および処理...
final_results_count = 0 # --- 新規:最終的な一致結果数をカウントするカウンター ---
try:
root = ET.fromstring(resp.http_response.content.decode('utf-8'))
# すべての File 要素を検索
files = root.findall('.//File')
print(f"\n OSS から {len(files)} 件の初期一致結果が得られました。クライアント側タグフィルターを開始します...")
if not files:
# NextContinuationToken の有無を確認し、ページングが必要な場合がある
next_token_elem = root.find('.//NextContinuationToken')
if next_token_elem is not None and next_token_elem.text:
print(" 注意:さらに結果がある可能性があります。現在の実装ではページングを処理していません。")
print("\n 初期一致結果はありません。")
return # ファイルがない場合は早期リターン
for i, file in enumerate(files, 1):
# ファイル名の取得
key_element = file.find('Filename')
if key_element is None:
print(f" 警告:{i} 番目の初期結果に Filename フィールドがありません。スキップします。")
continue
key = key_element.text
# --- クライアント側タグフィルター ---
if target_tag:
try:
tagging_req = GetObjectTaggingRequest(bucket=args.bucket, key=key)
tagging_resp = client.get_object_tagging(tagging_req)
# 返されたタグセットにターゲットタグが含まれているか確認
tag_found = False
target_k, target_v = target_tag.split('=', 1)
if tagging_resp.tag_set and tagging_resp.tag_set.tags: # .tags を使用
for tag in tagging_resp.tag_set.tags: # .tags を使用
if tag.key == target_k and tag.value == target_v:
tag_found = True
break
if not tag_found:
continue # タグが一致しないため、このファイルをスキップ
except oss.exceptions.ServiceError as tag_err:
if tag_err.status_code == 404 and tag_err.error_code == 'NoSuchTagSet':
continue
else:
print(f" 警告:ファイル '{key}' のタグ取得中にエラーが発生しました: {tag_err.error_code} - {tag_err.message}。スキップします。")
continue
except Exception as tag_e:
print(f" 警告:ファイル '{key}' のタグ取得または処理中に予期しないエラーが発生しました: {tag_e}。スキップします。")
# --- デバッグ用にトレースバックを追加 ---
import traceback
traceback.print_exc()
# --- 追加終了 ---
continue
# --- クライアント側タグフィルター終了 ---
# --- タグフィルターを通過した(またはタグフィルターが未設定)場合、結果を処理して表示 ---
final_results_count += 1 # 最終結果カウンターをインクリメント
print(f"\n[{final_results_count}] ファイル '{key}' がすべての条件に一致しました:") # 最終結果番号を表示
# 署名付き URL の生成
try:
pre_url = client.presign(
oss.GetObjectRequest(
bucket=args.bucket,
key=key,
)
)
print(format_result(key, pre_url.url))
except Exception as presign_e:
print(f" 警告:ファイル '{key}' の署名付き URL 生成中にエラーが発生しました: {presign_e}")
print(format_result(key, "[URL を生成できませんでした]"))
# --- ループ終了後に最終統計を表示 ---
print(f"\n クライアント側フィルター処理が完了しました。最終的な一致結果は {final_results_count} 件です。")
except ET.ParseError as xml_e:
print(f" エラー:OSS 応答 XML の解析エラー - {xml_e}")
except Exception as parse_e:
print(f" エラー:結果処理中に予期しないエラーが発生しました - {parse_e}")
if __name__ == "__main__":
perform_search()特定のカメラで撮影された裏庭の映像をフィルターするには、プログラムを実行し、以下の内容を入力します。
意味説明:
a yard with a parked carタグフィルター:
camera=camera-a
この例では、VideoA と VideoC の両方に説明に一致するシーンが含まれています。タグフィルターにより、VideoA(camera-a というタグが付与)のみが残り、最終結果には 1 件のファイルが含まれます。
出力例:
DoMetaQuery リクエストを送信中...
リクエストが成功しました。HTTP ステータスコード: 200
OSS から 2 件の初期一致結果が得られました。クライアント側タグフィルターを開始します...
[1] ファイル 'VideoA.mp4' がすべての条件に一致しました:
ファイルアドレス: https://ipc-videos-oss-metaquery-demo.oss-cn-beijing.aliyuncs.com/VideoA.mp4?x-oss-signature-version=OSS4-HMAC-SHA256&x-oss-date=20250526T054908Z&x-oss-expires=900&x-oss-credential=LTAI********************%2Fcn-beijing%2Foss%2Faliyun_v4_request&x-oss-signature=01bbf29790763d8e0f177d4cb0469cb00ae1c69d565219edb3866f75110b37ab
ファイルパス: VideoA.mp4
-----------------------
クライアント側フィルター処理が完了しました。最終的な一致結果は 1 件です。



