Ollama の Models を眺めていたら、「deepseek-ocr」というモデルがあるのに気づく。
論文はこれのようだ。
「DeepSeek-OCRは、単なる文字認識(OCR)モデルではなく、大規模言語モデル(LLM)の長文コンテキスト処理における課題を解決するため、ドキュメントを 「画像」 として超圧縮し、その軽量な ビジョン・トークン をLLMに渡して解読させるという、効率化に特化した次世代のOCRモデル」だそうです。
「他のvision 系モデルと同じように画像をインプットすれば動くのか?」と思い、早速試した。
なんか普通にOCRしてくれる。おもしろい。
import ollama
import re
from PIL import Image, ImageDraw
import json
target_image_path = "input.jpg" # OCR対象画像のパスを指定
response = ollama.chat(
model='deepseek-ocr:3b',
messages=[
{
"role": "user",
"content": "Free OCR.",
"images": [target_image_path],
},
])
print(response['message']['content'])
プロンプト
DeepSeek-OCR の説明を読むと、「モデルは入力データに敏感ですのでご注意ください」と書かれている。いくつかのプロンプトを試した結果、最も実用的だと感じたのは「<|grounding|>Convert the document to markdown.」だ。
- "content": "Free OCR.",
+ "content": "<|grounding|>Convert the document to markdown.",
このプロンプトを使うと、ただのテキスト抽出に留まらず、ドキュメント構造を理解したタグと、OCRの結果をMarkdown 形式で出力してくれる。これは記事のレイアウトや、コードの整理などに非常に役立つ。
<|ref|>title<|/ref|><|det|>[[205, 133, 789, 157]]<|/det|>
# DeepSeek-OCR: Contexts Optical Compression
「少しクセがあるな」という感じだが、タグを使えばtextだけとか、imageだけとか切り分けられそうだ。
タグ解析
この、クセありのテキストから情報を抽出する必要がある。
なんとなくだが、
-
<\|ref\|>と\|/ref\|>で囲まれた部分がタグ -
<\|det\|>と<\|/det\|>で囲まれた部分が対象の領域 - その後ろが対象のコンテンツ
っぽい。tableはなんとなく入れ子の感じがするが、とりあえず今日はスルー
これを抽出する方法をLLMに教えてもらい作成したコードが以下のような感じ
(いや、もうなんでもLLMだな。今日はCopilotさんに聞いたよ)
text = response['message']['content']
pattern = re.compile(
r"<\|ref\|>(.*?)<\|/ref\|>\s*<\|det\|>(.*?)<\|/det\|>\s*(.*?)(?=<\|ref\||$)",
re.DOTALL
)
results = []
for ref, det, content in pattern.findall(text):
results.append({
"tag": ref.strip(),
"block": json.loads(det.strip()),
"content": content.strip()
})
座標
抽出した座標を元の画像に当てると、ズレた場所に表示された。しかし、ボックスの並びの形自体は悪くない。そして、なんとなく正方形な感じがする。
「もしかして、画像に対して比率が出力されている?」
そう直感して、画像表示と座標の比率を調整してみたところ、見事にドンピシャの位置に矩形が表示された!
# 結果を確認
img = Image.open(target_image_path)
width, height = img.size
draw = ImageDraw.Draw(img)
for r in results:
block_info = r['block']
x1, y1, x2, y2 = block_info[0]
# 画像サイズに基づいて座標をスケーリング
block = [x1 * width/1000, y1 * height/1000, x2 * width/1000, y2 * height/1000]
if(r['tag'] == 'image'):
color = "red"
elif(r['tag'] == 'image_caption'):
color = "magenta"
elif(r['tag'] == 'title'):
color = "gray"
elif(r['tag'] == 'sub_title'):
color = "gray"
elif(r['tag'] == 'text'):
color = "black"
else:
color = "blue"
draw.rectangle(block, outline=color, width=3)
img.save("output_with_boxes.jpg")
img.show()
まとめ
良い感じにレイアウト解析をしてくれるやつがなかなか見つからなかったが、なかなかいい感じな気がするツールを見つけた。
日本語の文章にも対応しているし、いろいろ試してみたい。
しかも、「MITライセンス」ということだ。すごい世の中だな~
