その他

【AIエージェント】マルチモーダルAIを使って画像認識とロゴ検出機能を実装してみた

その他
この記事は約9分で読めます。

前回のおさらい

こんにちは。

前回はロゴ画像を照合するシステムとして、画像処理技術のテンプレートマッチングという手法を紹介しました。

テンプレートマッチングとは、用意した教師画像(テンプレート画像)を探索対象である画像から走査して検出結果を矩形として表示する手法です。

テンプレートマッチングをさせてみた結果、弊社ロゴは良い感じに検出することができています。

前回の記事は下記に添付してありますので、ぜひ一読ください!

テンプレート画像
走査対象画像
検出結果

今回やること

近年、画像や音声などの非構造データを処理できる「マルチモーダルAI」が急速に進化しています。

特に、OpenAIが提供するモデルでは、画像をそのまま入力し、その内容や意味をテキストとして推論できるようになってきました。

そこで今回はこの技術を応用し、画像の中からロゴと思われる領域を自動的に検出してくれるAIエージェントを構築してみます。

AIエージェントモデル

マルチモーダルAI

早速「マルチモーダルAI」という、聞き慣れない言葉が出てきましたね。

これは、従来のようにテキストや画像、音声など単一のデータに限定せず、複数のコンテンツのデータを同時に扱えるモデルを指します。

マルチモーダルAI

最近ではマルチモーダルモデルが登場し、画像に含まれる色・形・輪郭・テキストなどを統合的に分析して、画像認識などできるようになっています。

つい最近までAIエージェントはテキストを理解して生成するモデルと、画像を認識するモデルなど複数のモデルを組み合わせて使っていると思っていました。

それが、ここまで拡張性の高い単一のモデルに進化していたとは思わず、AI技術の進歩の速さを改めて実感しています。

gpt-4o

今回のAIエージェント開発で使用しているモデルは、OpenAI が提供しているgpt-4o です。

普段から利用している方も多いと思いますが、gpt-4oはマルチモーダル技術で作られており、しかも無料で使えるという点は驚きですよね。

ただしgpt-4oは、画像からロゴを検出する処理や矩形描画といった直接的な画像処理には対応できないため、あくまでも画像の内容を認識する役割として使用しています。

検出処理

テキスト抽出(EasyOCR)

EasyOCRとは画像の中からテキスト情報を抽出することができるオープンソースライブラリです。

ロゴには企業名やブランド名など、文字情報が含まれていることが多いため、画像の中から特定の図形や模様を探すよりも、まずはテキストを検出する方が効率的だと考えました。

また、EasyOCRの出力には 文字列の位置座標と信頼度 が含まれており、そのまま矩形として画像に描画しやすい構造になっているのも特徴です。

import cv2, sys
import easyocr 

img_path = sys.argv[1]

img = cv2.imread(img_path, cv2.IMREAD_COLOR)

reader = easyocr.Reader(['ja', 'en'])

results = reader.readtext(img)

for (bbox, text, prob) in results:
    # 座標を取得(4点)
    top_left = tuple(map(int, bbox[0]))
    bottom_right = tuple(map(int, bbox[2]))

    # 矩形を描画
    cv2.rectangle(img, top_left, bottom_right, (0, 255, 0), 2)

    # テキストを描画
    cv2.putText(img, text, (top_left[0], top_left[1] - 10),
    cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 0, 255), 2)

# 出力ファイル名を生成
output_path = "output_with_text.jpg"

# 画像を保存
cv2.imwrite(output_path, img)

輪郭抽出

OCRで得たテキスト領域だけに依存せず、テキストベースのロゴ検出も併用するための前処理として、エッジ検出と輪郭抽出を行います。

輪郭抽出は画像処理において非常に重要な処理で、画像から不要な情報を削減するだけでなく、オブジェクトの形状を得ることができます。

特に機械学習では、輪郭など多数の特徴量を学習することで、物体の分類や認識精度を高めることが可能です。

gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
edges = cv2.Canny(gray, 50, 150)
contours, _ = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

実装

ここまで紹介してきた仕組みを、実際にAIエージェントとして動作させるために統合しました。
実装は Streamlit をベースにしており、Webアプリのように手軽に動かせる構成になっています。

モデル定義

AIエージェントを動作させるためには、temperature・モデル名・max_tokens を設定する必要があります。

まず、temperatureはモデルの返答が機械的か人間的か自由に選ぶことができますが、トークンの消費を抑えたいので0にしました。

次に、モデルをgpt-4oとし、max_tokensは今回のモデルでは最大16384トークンまで扱うことができます。

しかし今回はそれほど大きな出力は想定していないため、1,000 に設定しています。

llm = ChatOpenAI(
        temperature = 0,
        model = "gpt-4o",
        max_tokens = 1000
    )

画像のエンコード

今回のチャット入力では、画像とテキストを同時に扱えるよう、アップロードした画像を base64 エンコード で文字列化しています。

これは、多くの API が JSON フォーマット で入力を受け付けており、画像ファイルをそのまま送信できないためです。

そこでbase64 に変換することで、画像を「テキストデータ」として安全に API に渡せるようになります。

API に入力された画像データはモデル内部で処理され、専用の エンコーダー によって数値ベクトルに変換され、モデルはテキストや画像といった異なる種類のデータを 共通の形式 として理解できるようになります。

最終的に得られたベクトルはテキストと同列に扱われ、マルチモーダルモデル内で統合的に処理されます。

import base64

upload_file = st.file_uploader(
    label = "Upload your Image here",
    type = [".jpg", ".png", ".webp", "gif"] #モデル次第で読み込めるフォーマットが変わる
)

image_base64 = base64.b64encode(upload_file.read()).decode()
print(image_base64)
image = f"data:image/jpeg;base64,{image_base64}"

検出処理

検出処理は、EasyOCR輪郭抽出を組み合わせたハイブリッド方式で行っています。

EasyOCRでは画像から文字情報を抽出し、企業名やブランド名のような文字を含むロゴを検出します。

ocr_results = reader.readtext(image)   # ← EasyOCRによるテキスト検出

for (bbox, text, confidence) in ocr_results:
    if confidence >= confidence_threshold:
        x_coords = [point[0] for point in bbox]
        y_coords = [point[1] for point in bbox]
        x1, y1 = int(min(x_coords)), int(min(y_coords))
        x2, y2 = int(max(x_coords)), int(max(y_coords))
        detections.append((x1, y1, x2, y2, float(confidence), f"Text: {text}"))

次にCanny処理でエッジ検出と輪郭抽出を行い、図形的な特徴からロゴらしい領域を抽出します。

edges = cv2.Canny(gray, 50, 150)
contours, _ = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) #輪郭抽出

for contour in contours:
    area = cv2.contourArea(contour)
    x, y, w, h = cv2.boundingRect(contour) #矩形領域
    overlap = False
    for det in detections:
     if not (x > det[2] or x+w < det[0] or y > det[3] or y+h < det[1]):
       overlap = True
       break

それぞれの検出結果を統合し、矩形を描画することでおよそロゴと思われる領域をアテンションすることがでできるようになりました。

#可視化
for i, (x1, y1, x2, y2, conf, label) in enumerate(detections):
	color = colors[i % len(colors)]
	cv2.rectangle(result_image, (x1, y1), (x2, y2), color, 4)
	cv2.putText(result_image, f'{label} {conf:.2f}', 
      (x1, y1-10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 2)

実行結果

検出に使用した画像は弊社ロゴ画像です。

ここまでの検出処理を経て、赤い四角形で示すようにロゴ領域を検出することができました。

一方で、緑の四角形の部分では壁に取り付けられたプレートの影が検出されるなど、ロゴ以外の要素も拾ってしまうケースもあります。

また、画像によってはテキストと背景の色が近いために抽出がうまくいかない場合なども想定されるため、機械学習ベースでの検出も有効かなと考えています。

検出結果の例

おわりに

いかがでしたでしょうか。

今回はマルチモーダルAIと、画像処理技術を用いて画像中からロゴと思われる領域を検出してくれるAIエージェントを作ってみました。

今回作成したソースコードは下記のgit hubから使用することができますが、OpenAI APIのみご自身のAPI KEYをご用意ください。

GitHub - morishitaimpl/logo_detection_AIagent: 画像中のロゴを検出するAIエージェント
画像中のロゴを検出するAIエージェント. Contribute to morishitaimpl/logo_detection_AIagent development by creating an account on GitHub.

参考URL

マルチモーダルAIとは?
注目の科学技術や社会課題、今さら聞けないことや、なかなか説明しづらい素朴な疑問を、産総研のプロフェッショナルが分かりやすく解説します!
base64を理解する。 - Qiita
base64とは base64とは、64進数を意味する言葉で、すべてのデータをアルファベット(a~z, A~z)と数字(0~9)、一部の記号(+,/)の64文字で表すエンコード方式です。 base64ってなんぞ??理解のために実装してみた 関係図 .py # 文...