はじめに
hugging faceのトレンドを見てたらFlorence-2-largeというモデルが上位に上がっていました。
![](https://cdn-thumbnails.huggingface.co/social-thumbnails/models/microsoft/Florence-2-large.png)
ReadMeを読んでみると、ImagetoTextのLLMのようですが、どうやら簡単なプロンプトから物体検出タスクを行うモデルのようです。
つまり、Yoloなどに変わる物体検出モデルとして使えるかもしれません。しかもYoloと違ってタスク事に学習しないでも、指示できるのです。
今回の記事ではこのFlorence 2を動かしてみて、どの程度のものが確かめてみます。
環境構築
dev-containerで試しました。以下、Dockerfileとdevcontainer.json、docker-compose.ymlです。
Dockerfile
前に作ったhugging faceの実行環境をベースに構築しました。
FROM nvcr.io/nvidia/pytorch:22.04-py3
ARG USERNAME=vscode
ARG USER_UID=1000
ARG USER_GID=$USER_UID
ENV LANG ja_JP.UTF-8
ENV LANGUAGE ja_JP:ja
ENV LC_ALL ja_JP.UTF-8
ENV TZ JST-9
ENV TERM xterm
RUN apt-get update \
&& groupadd --gid $USER_GID $USERNAME \
&& useradd -s /bin/bash --uid $USER_UID --gid $USER_GID -m $USERNAME \
&& apt-get install -y sudo \
&& echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME \
&& chmod 0440 /etc/sudoers.d/$USERNAME \
&& apt-get -y install locales \
&& localedef -f UTF-8 -i ja_JP ja_JP.UTF-8
RUN apt-get -y install git
RUN pip install --upgrade pip
RUN pip install --upgrade setuptools
RUN python -m pip install \
torchvision \
torchaudio \
invisible_watermark
RUN python -m pip install \
accelerate \
datasets \
hf-doc-builder \
huggingface-hub \
Jinja2 \
librosa \
numpy \
scipy \
tensorboard \
transformers \
pytorch-lightning
RUN python -m pip install \
omegaconf~=2.3.0 \
tqdm \
einops \
vector_quantize_pytorch \
vocos \
IPython \
nemo_text_processing \
gradio
RUN python -m pip install \
timm flash_attn
ENV HF_HOME /work/.cache/huggingface
ENV TORCH_HOME /work/.cache/torchvision
devcontainer.json
名前とサービス名を決定してください。また、必要な拡張機能を入れてください。
{
"name": "transformers-test",
"service": "transformers-test",
"dockerComposeFile": "docker-compose.yml",
"remoteUser": "vscode",
"workspaceFolder": "/work",
"customizations": {
"vscode": {
"extensions": [
"ms-python.python",
"ms-toolsai.jupyter"
]
}
}
}
docker-compose.yml
設定したサービス名を間違えないでください。
また、GPUを使うので、GPUが使えるようにdeployを書き足します。
version: '3'
services:
transformers-test:
container_name: 'transformers-test-container'
hostname: 'transformers-test-container'
build: .
restart: always
working_dir: '/work'
tty: true
volumes:
- type: bind
source: ..
target: /work
ulimits:
memlock: -1
stack: -1
shm_size: '10gb'
deploy:
resources:
reservations:
devices:
- capabilities: [gpu]
実行して処理を見る
まずは、ReadMeに書いてあったサンプルコードを動かしてみます。
import requests
from PIL import Image
from transformers import AutoProcessor, AutoModelForCausalLM
model = AutoModelForCausalLM.from_pretrained("microsoft/Florence-2-large", trust_remote_code=True)
processor = AutoProcessor.from_pretrained("microsoft/Florence-2-large", trust_remote_code=True)
prompt = "<OD>"
url = "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/transformers/tasks/car.jpg?download=true"
image = Image.open(requests.get(url, stream=True).raw)
inputs = processor(text=prompt, images=image, return_tensors="pt")
generated_ids = model.generate(
input_ids=inputs["input_ids"],
pixel_values=inputs["pixel_values"],
max_new_tokens=1024,
num_beams=3,
do_sample=False
)
generated_text = processor.batch_decode(generated_ids, skip_special_tokens=False)[0]
parsed_answer = processor.post_process_generation(generated_text, task="<OD>", image_size=(image.width, image.height))
print(parsed_answer)
初回実行時はモデルのダウンロードがあるので、少し待ちます。、モデルサイズは1.5GB程度でした。
実行すると次の出力がでてきました。
{'<OD>': {'bboxes': [[34.23999786376953, 160.0800018310547, 597.4400024414062, 371.7599792480469], [456.0, 97.68000030517578, 580.1599731445312, 261.8399963378906], [450.8800048828125, 276.7200012207031, 554.5599975585938, 370.79998779296875], [95.68000030517578, 280.55999755859375, 198.72000122070312, 371.2799987792969]], 'labels': ['car', 'door', 'wheel', 'wheel']}}
Boxesの位置とクラスが出力されたので、どうやら'<OD>’はObject Detection(物体検出)のタスクを行うプロンプトのようです。
各関数の役割と、その実行時間を軽く見てみます。
import requests
from PIL import Image
from transformers import AutoProcessor, AutoModelForCausalLM
import time
model = AutoModelForCausalLM.from_pretrained("microsoft/Florence-2-large", trust_remote_code=True)
processor = AutoProcessor.from_pretrained("microsoft/Florence-2-large", trust_remote_code=True)
prompt = "<OD>"
url = "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/transformers/tasks/car.jpg?download=true"
image = Image.open(requests.get(url, stream=True).raw)
s = time.time()
inputs = processor(text=prompt, images=image, return_tensors="pt")
print(f"inputs : {inputs}")
print(f"processor time : {time.time()- s}")
s = time.time()
generated_ids = model.generate(
input_ids=inputs["input_ids"],
pixel_values=inputs["pixel_values"],
max_new_tokens=1024,
num_beams=3,
do_sample=False
)
print(f"generated_ids : {generated_ids}")
print(f"model.generate time : {time.time()- s}")
s = time.time()
generated_text = processor.batch_decode(generated_ids, skip_special_tokens=False)[0]
print(f"generated_text : {generated_text}")
print(f"processor.batch_decode time : {time.time()- s}")
s = time.time()
parsed_answer = processor.post_process_generation(generated_text, task="<OD>", image_size=(image.width, image.height))
print(f"parsed_answer : {parsed_answer}")
print(f"processor.post_process_generation time : {time.time()- s}")
実行すると次の出力が得られました。
inputs : {'input_ids': tensor([[ 0, 574, 22486, 5, 8720, 19, 4120, 766, 11, 5,
2274, 4, 2]]), 'attention_mask': tensor([[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]]), 'pixel_values': tensor([[[[-1.1418, -0.7479, -0.9192, ..., -1.9295, -1.9467, -1.9638],
[-0.7650, -0.7137, -0.9192, ..., -1.9295, -1.9467, -1.9638],
[-0.2684, -0.7308, -0.9705, ..., -1.9467, -1.9638, -1.9809],
...,
[ 0.5707, 0.5707, 0.5707, ..., 0.3481, 0.3481, 0.3652],
[ 0.5707, 0.5707, 0.5707, ..., 0.3823, 0.3823, 0.3652],
[ 0.5707, 0.5707, 0.5707, ..., 0.4166, 0.3994, 0.3823]],
[[-1.3880, -0.9853, -1.1604, ..., -1.6506, -1.6681, -1.6856],
[-1.0028, -0.9503, -1.1604, ..., -1.6506, -1.6681, -1.6856],
[-0.4951, -0.9678, -1.2129, ..., -1.6681, -1.6856, -1.7031],
...,
[ 0.6078, 0.6078, 0.6078, ..., 0.3978, 0.3978, 0.4153],
[ 0.6078, 0.6078, 0.6078, ..., 0.4328, 0.4328, 0.4153],
[ 0.6078, 0.6078, 0.6078, ..., 0.4678, 0.4503, 0.4328]],
[[-1.1770, -0.7761, -0.9504, ..., -1.4907, -1.5081, -1.5256],
[-0.7936, -0.7413, -0.9504, ..., -1.4907, -1.5081, -1.5256],
[-0.2881, -0.7587, -1.0027, ..., -1.5081, -1.5256, -1.5430],
...,
[ 0.8274, 0.8274, 0.8274, ..., 0.5659, 0.5659, 0.5834],
[ 0.8274, 0.8274, 0.8274, ..., 0.6008, 0.6008, 0.5834],
[ 0.8274, 0.8274, 0.8274, ..., 0.6356, 0.6182, 0.6008]]]])}
processor time : 0.07494592666625977
generated_ids : tensor([[ 2, 0, 0, 0, 5901, 50322, 50602, 51202, 51043, 11219,
50981, 50472, 51175, 50814, 13630, 50973, 50845, 51135, 51041, 50418,
50853, 50579, 51042, 2]])
model.generate time : 5.702800273895264
generated_text : </s><s><s><s>car<loc_53><loc_333><loc_933><loc_774>door<loc_712><loc_203><loc_906><loc_545>wheel<loc_704><loc_576><loc_866><loc_772><loc_149><loc_584><loc_310><loc_773></s>
processor.batch_decode time : 0.00020122528076171875
parsed_answer : {'<OD>': {'bboxes': [[34.23999786376953, 160.0800018310547, 597.4400024414062, 371.7599792480469], [456.0, 97.68000030517578, 580.1599731445312, 261.8399963378906], [450.8800048828125, 276.7200012207031, 554.5599975585938, 370.79998779296875], [95.68000030517578, 280.55999755859375, 198.72000122070312, 371.2799987792969]], 'labels': ['car', 'door', 'wheel', 'wheel']}}
processor.post_process_generation time : 0.0013086795806884766
それぞれの出力された変数と関数の役割についてまとめます。
関数名 | 出力変数 | 役割 | 実行時間(sec) |
processor | inputs | 画像をタスク(プロンプト)によって情報が埋め込まれたベクトルへ変換 | 0.0749 |
model.generate | generated_ids | おそらくトークンidのリストを生成 | 5.7028 |
processor.batch_decode | generated_text | トークンidからトークンにデコード | 0.0002 |
processor.post_process_generation | parsed_answer | トークンリストを整形して、辞書にする | 0.0013 |
なるほど、文字(トークン)のリストとして物体検出の情報を吐き出しているみたいですね。
つまり、未知(トークンとして学習していない)の物体は検出できないようです。
他のタスクへの適用
ipynb形式のサンプルコードも置いてありました。複数のタクスで推論してみた例を載せています。
検証しているタスクを取り上げると、
- テキスト情報無しで推論
- キャプション (‘<CAPTION>’) : 画像の説明を文章で出力
- 物体検出 (‘<OD>’) : 物体検出。ラベルと物体のボックスデータを吐き出す
- 物体検知とキャプション (‘<DENSE_REGION_CAPTION>’) : 物体を検出してそれぞれの説明をする
- 領域検出 (‘<REGION_PROPOSAL>’) : 分けられそうなボックス領域を提案してくれる
- テキスト情報ありで推論
- キャプションから物体検出 (‘<CAPTION_TO_PHRASE_GROUNDING>’) : キャプションを与えると、画像からそのボックス領域を検出
- 対象の領域を塗る (‘<
- REFERRING_EXPRESSION_SEGMENTATION>’) : 対象をテキストで与えると、その領域を塗れる
- ボックスデータからの領域検出 (‘<REGION_TO_SEGMENTATION>’) : ボックスデータを与えると、写っている領域を塗ってくれる
- 物体検出とOCR (‘<OPEN_VOCABULARY_DETECTION>’) : 物体と文字を検出する
- OCR
- 単純なOCR (‘<OCR>’) : 画像中の文字を検出
- 領域とOCR (‘<OCR_WITH_REGION>’) : 文字とその領域を検出
などがありました。かなり多くのタスクができるようですね。
すこし手元の画像でも試してみましょう。画像はpexelsからもらいました。
物体検出タスク
タスクODを試してみます。
import requests
from PIL import Image
from transformers import AutoProcessor, AutoModelForCausalLM
import matplotlib.pyplot as plt
import matplotlib.patches as patches
model = AutoModelForCausalLM.from_pretrained("microsoft/Florence-2-large", trust_remote_code=True)
processor = AutoProcessor.from_pretrained("microsoft/Florence-2-large", trust_remote_code=True)
prompt = "<OD>"
task = prompt
url = "https://images.pexels.com/photos/26202784/pexels-photo-26202784.jpeg?cs=srgb&dl=pexels-diogo-miranda-2044514-26202784.jpg&fm=jpg"
image = Image.open(requests.get(url, stream=True).raw)
inputs = processor(text=prompt, images=image, return_tensors="pt")
generated_ids = model.generate(
input_ids=inputs["input_ids"],
pixel_values=inputs["pixel_values"],
max_new_tokens=1024,
num_beams=3,
do_sample=False
)
generated_text = processor.batch_decode(generated_ids, skip_special_tokens=False)[0]
parsed_answer = processor.post_process_generation(generated_text, task=task, image_size=(image.width, image.height))
fig, ax = plt.subplots()
ax.imshow(image)
for bbox, label in zip(parsed_answer[task]['bboxes'], parsed_answer[task]['labels']):
x1, y1, x2, y2 = bbox
rect = patches.Rectangle((x1, y1), x2-x1, y2-y1, linewidth=1, edgecolor='r', facecolor='none')
ax.add_patch(rect)
plt.text(x1, y1, label, color='white', fontsize=8, bbox=dict(facecolor='red', alpha=0.5))
ax.axis('off')
plt.savefig('test.jpg')
ちゃんと犬が検出できました。
![](https://emoclework.jp/wp-content/uploads/2024/06/test-1.jpg)
精度も悪く無いと思います。さすがにfine-turningしたYoloのほうが精度は良さそうですが。
色塗り
物体の領域に色を塗ってみたいと思います。
import requests
from PIL import Image
from transformers import AutoProcessor, AutoModelForCausalLM
from PIL import Image, ImageDraw
import numpy as np
model = AutoModelForCausalLM.from_pretrained("microsoft/Florence-2-large", trust_remote_code=True)
processor = AutoProcessor.from_pretrained("microsoft/Florence-2-large", trust_remote_code=True)
prompt = "A green river"
task = "<REFERRING_EXPRESSION_SEGMENTATION>"
url = "https://images.pexels.com/photos/25649817/pexels-photo-25649817.jpeg?cs=srgb&dl=pexels-daniel-guti-181008543-25649817.jpg&fm=jpg"
image = Image.open(requests.get(url, stream=True).raw)
inputs = processor(text=task+prompt, images=image, return_tensors="pt")
generated_ids = model.generate(
input_ids=inputs["input_ids"],
pixel_values=inputs["pixel_values"],
max_new_tokens=1024,
num_beams=3,
do_sample=False
)
generated_text = processor.batch_decode(generated_ids, skip_special_tokens=False)[0]
parsed_answer = processor.post_process_generation(generated_text, task=task, image_size=(image.width, image.height))
print(parsed_answer)
draw = ImageDraw.Draw(image)
scale = 1
for polygons, label in zip(parsed_answer[task]['polygons'], parsed_answer[task]['labels']):
color = 'blue'
for _polygon in polygons:
_polygon = np.array(_polygon).reshape(-1, 2)
if len(_polygon) < 3:
print('Invalid polygon:', _polygon)
continue
_polygon = (_polygon * scale).reshape(-1).tolist()
draw.polygon(_polygon, outline=color, fill=color)
image.save('test2.jpg', 'JPEG')
塗れなかった箇所がありますが、人を避けて川を塗ってくれました。
![](https://emoclework.jp/wp-content/uploads/2024/06/test2-scaled.jpg)
垂直に塗れない箇所が来ているので、ボックス領域として検出する他のタスクの影響を受けているのかもしれません。もしくは、画像をスプリットしてLLMに突っ込む仕様なのかもしないですね。
OCR
最後にOCRを試してみます。
import requests
from PIL import Image
from transformers import AutoProcessor, AutoModelForCausalLM
from PIL import Image, ImageDraw
import numpy as np
import random
colormap = ['blue','orange','green','purple','brown','pink','gray','olive','cyan','red',
'lime','indigo','violet','aqua','magenta','coral','gold','tan','skyblue']
model = AutoModelForCausalLM.from_pretrained("microsoft/Florence-2-large", trust_remote_code=True)
processor = AutoProcessor.from_pretrained("microsoft/Florence-2-large", trust_remote_code=True)
prompt = "<OCR_WITH_REGION>"
task = prompt
url = "https://images.pexels.com/photos/241832/pexels-photo-241832.jpeg?cs=srgb&dl=pexels-hermaion-241832.jpg&fm=jpg"
image = Image.open(requests.get(url, stream=True).raw)
inputs = processor(text=prompt, images=image, return_tensors="pt")
generated_ids = model.generate(
input_ids=inputs["input_ids"],
pixel_values=inputs["pixel_values"],
max_new_tokens=1024,
num_beams=3,
do_sample=False
)
generated_text = processor.batch_decode(generated_ids, skip_special_tokens=False)[0]
parsed_answer = processor.post_process_generation(generated_text, task=task, image_size=(image.width, image.height))
print(parsed_answer)
scale = 1
draw = ImageDraw.Draw(image)
bboxes, labels = parsed_answer[task]['quad_boxes'], parsed_answer[task]['labels']
for box, label in zip(bboxes, labels):
color = random.choice(colormap)
new_box = (np.array(box) * scale).tolist()
draw.polygon(new_box, width=3, outline=color)
draw.text((new_box[0]+8, new_box[1]+2),
"{}".format(label),
align="right",
fill=color)
print(box, label)
image.save('test3.jpg', 'JPEG')
かなり煩雑に並んだ文字ですが、全ては検出できなかったものの、半分くらいは検出できました。
![](https://emoclework.jp/wp-content/uploads/2024/06/test3-scaled.jpg)
パラメーターを調整すれば、もっと行けそうな気もします。
まとめ
今回は、マルチモーダルLLM(?)のFlorence 2を使って学習しないで物体検出してみました。
かなりモデルサイズが小さいので、スマホに搭載することも十分できると思われます。今後のスマホアプリは、こういう物体検出アプリがでてきそうですね。
また、小さいモデルながら、様々なタスクをできる手軽さも、Yoloと比べたときの利点だと思われます。タスクごとに追加学習のデータを用意しなくても良いのはいいですね。
しかし、精度はモデル相応だったとも感じました。精度は今後に期待します。