Moondream 1.9B modelis
2025.gada 9.janvārī uzņēmums M87 Labs izlaida jaunu atvērtā koda attēlu modeli (vision language model) Moondream 1.9B.
Modelis spēj:
- atbildēt uz jautājumiem, kas attēlots attēlā (VQA — visual question answering),
- ģenerēt attēla aprakstu (image captioning),
- identificēt objektus attēlā (object detection),
- norādīt uz identificētajiem objektiem (visual pointing),
- galarezultāta aprakstu iedot strukturētā (JSON, XML, Markdown vai CSV) formātā,
- identificēt skatienu (gaze detection),
- identificēt tekstu no attēla (OCR).
Šajā rakstā aplūkosim sīkāk šī modeļa iespējas. Par pamatu izmantoju dokumentācijā norādīto Python kodu un pašu risinājumu darbināju Google Colab uz Linux mašīnas. Sākotnēji mēģināju strādāt ar GitHub Codespaces, taču bezmaksas 16 GiB RAM (CPU) izrādījās nepietiekami, lai nodrošinātu ātru attēlu apstrādi ar šo modeli.
Šoreiz izvēlējos strādāt ar Linux, jo modeļa darbināšanai uz Windows mašīnas ir nepieciešams veikt vēl papildu darbības (skat. šeit: “For Windows users”).
Vispirms ielādējam nepieciešamās Python bibliotēkas:
pip install pyvips-binary pyvips transformers einops
Tad Python programmā vai, kā manā gadījumā, Google Colab, ielādējam darbam vajadzīgās Python pakotnes:
from datetime import datetime
from io import BytesIO
from IPython.display import display # For Google Colab
import matplotlib.pyplot as plt
import numpy as np
from PIL import Image, ImageDraw
import requests
from transformers import AutoModelForCausalLM, AutoTokenizer # pip install transformers
Pēc tam jau var lejuplādēt modeli. Ja koda izpildei tiek izmantots GPU, nepieciešams norādīt device_map={“”: “cuda”}
:
model = AutoModelForCausalLM.from_pretrained(
"vikhyatk/moondream2", # https://huggingface.co/vikhyatk/moondream2
revision="2025-01-09",
trust_remote_code=True,
device_map={"": "cuda"}
)
Ielādējam attēlu. Modeļa testēšanā izmantoju “Distracted Boyfriend Meme” bildi no imgflip:
# Load image #1
url1 = "https://imgflip.com/s/meme/Distracted-Boyfriend.jpg"
response1 = requests.get(url1)
image1 = Image.open(BytesIO(response1.content))
display(image1) # This works in Google Colab
Pārkodējam attēlu. Šī darbība uz CPU notiek ļoti ilgi, bet uz GPU dažas sekundes (~1 sek Google Colab gadījumā):
encoded_image1 = model.encode_image(image1)
Attēla apraksta ģenerēšana
Apraksta ģenerēšanā izmanto model.caption
metodi un tajā norāda vienu no diviem iespējamiem apraksta garuma veidiem. Īsa apraksta gadījumā norādi length=”short”
, garāka apraksta gadījumā atribūta length
vērtība ir “normal”, bet to var arī nenorādīt, jo tā ir noklusētā vērtība:
model.caption(encoded_image1, length="short")["caption"]
Jautājumu uzdošana
Jautājuma uzdošanai izmanto model.query
metodi:
model.query(encoded_image1, "What woman in a blue blouse is doing?")["answer"]
Objektu identificēšana
Objektu identificēšanai izmanto model.detect
metodi:
model.detect(encoded_image1, "How many long haired women are in this picture?")
Kā redzams, tad atgrieztais rezultāts ir saraksts ar vārdnīcu. Tā kā ir identificēta tikai viena persona (kas ir kļūdaini), tad sarakstā ir tikai viena vārdnīca, kurā norādītas divu punktu x un y koordinātas.
Lai pārliecinātos par atgrieztā rezultāta pareizību, izmantojam šīs koordinātas, lai uz attēla uzzīmētu taisnstūri:
width, height = image1.size
image1_copy = image1.copy()
draw = ImageDraw.Draw(image1_copy)
for obj in detect_result["objects"]:
x_min = int(obj['x_min'] * width)
y_min = int(obj['y_min'] * height)
x_max = int(obj['x_max'] * width)
y_max = int(obj['y_max'] * height)
# Draw rectangle (red, thickness 3)
draw.rectangle([x_min, y_min, x_max, y_max], outline="red", width=3)
# Show the modified image
display(image1_copy) # This works in Google Colab
Lai ar draw.rectangle
uzzīmētu taisnstūri, ir nepieciešami 2 punkti: apakšējais kreisais kreisais stūris (x_min, y_min) un augšējais labais stūris (x_max, y_max). Redzams, ka modelis nespēja atrast otru “sievieti ar gariem matiem” kā tika uzdots model.detect
metodē:
Norāde uz objektiem (point at object)
Objektu norādei izmanto model.point
metodi. Pēc nosaukuma jau ir skaidrs, ka objektu identificēšanā modelis iedos punkta koordinātas.
model.point(encoded_image1, "How many arms do you see in this picture?")
Lai pārliecinātos par atgrieztā rezultāta pareizību, izmantojam šīs koordinātas, lai uz attēla uzzīmētu punktus:
image2_copy = image1.copy()
draw = ImageDraw.Draw(image2_copy)
# Set point size
radius = 5
# Draw each point
for point in point_result["points"]:
x = int(point['x'] * width)
y = int(point['y'] * height)
# Draw a small circle (dot)
draw.ellipse([x - radius, y - radius, x + radius, y + radius], fill="blue", outline="black")
# Show the modified image
display(image2_copy) # This works in Google Colab
Redzams, ka modelim ir ievērojami vieglāk identificēt “rokas”, ja tās ir skaidri saskatāmas. Savukārt vietās, kur attēls nav pietiekami fokusēts, modelis pieļauj visvairāk kļūdu.
Skatiena identificēšana (gaze detection)
Skatiena identificēšanai ir jāveic vairāki soļi. Vispirms ar model.detect
metodi atrodam seju(-as). Papildus izveidoju arī colors
objektu, lai katra no identificētām sejām tiktu “ierāmēta” ar citas krāsas taisnstūri:
detection_result_faces = model.detect(encoded_image1, "face")
faces = sorted(detection_result_faces["objects"], key=lambda f: (f["y_min"], f["x_min"]))
colors = plt.cm.rainbow(np.linspace(0, 1, max(1, len(faces))))
Tālāk, ar model.detect_gaze
metodes palīdzību meklējam “kur šī seja skatās”, un kā viens no obligātajiem šīs metodes atribūtiem ir sejas atrašanās vieta (face_center
). Līdzīgi kā model.point
arī model.detect_gaze
atgriež punkta koordinātas, jeb skatiena punktu. Ja model.detect_gaze
atgriež None
, tad skatiena punkts nav atrasts.
image3_copy = image1.copy()
draw = ImageDraw.Draw(image3_copy)
# Process each face
for face, color in zip(faces, colors):
try:
# Calculate face box coordinates
x_min = int(float(face["x_min"]) * width)
y_min = int(float(face["y_min"]) * height)
x_max = int(float(face["x_max"]) * width)
y_max = int(float(face["y_max"]) * height)
face_border_width = int(float(face["x_max"] - face["x_min"]) * width)
face_border_height = int(float(face["y_max"] - face["y_min"]) * height)
# Draw face bounding box
face_color = tuple(int(c * 255) for c in color[:3]) # Convert color from (0-1) to (0-255)
draw.rectangle([x_min, y_min, x_max, y_max], outline=face_color, width=3)
# Calculate face center
face_center = (
float(face["x_min"] + face["x_max"]) / 2,
float(face["y_min"] + face["y_max"]) / 2,
)
# Try to detect gaze
try:
gaze_result = model.detect_gaze(encoded_image1, face_center)
if isinstance(gaze_result, dict) and "gaze" in gaze_result:
gaze = gaze_result["gaze"]
print(f'Gaze: {gaze}')
else:
gaze = gaze_result
except Exception as e:
print(f"Error detecting gaze: {e}")
continue
if (
gaze is not None
and isinstance(gaze, dict)
and "x" in gaze
and "y" in gaze
):
gaze_x = int(float(gaze["x"]) * width)
gaze_y = int(float(gaze["y"]) * height)
face_center_x = x_min + face_border_width // 2
face_center_y = y_min + face_border_height // 2
# Draw gaze line with gradient effect
points = 50
alphas = np.linspace(0.8, 0, points)
# Calculate points along the line
x_points = np.linspace(face_center_x, gaze_x, points)
y_points = np.linspace(face_center_y, gaze_y, points)
# Draw gradient line segments
for i in range(points - 1):
alpha = int(255 * alphas[i]) # Convert to 0-255
segment_color = (face_color[0], face_color[1], face_color[2], alpha) # RGBA
# Draw segment line
draw.line([(x_points[i], y_points[i]), (x_points[i + 1], y_points[i + 1])], fill=segment_color, width=2)
# Draw gaze point
radius = 5 # Size of gaze point
draw.ellipse([gaze_x - radius, gaze_y - radius, gaze_x + radius, gaze_y + radius], fill=face_color, outline="black")
except Exception as e:
print(f"Error processing face: {e}")
continue
# Show the modified image
display(image3_copy) # This works in Google Colab
Augstāk norādītajā kodā draw.line
izmantots, lai sejas viduspunktu (face_center
) savienotu ar skatiena punktu, kurš, savukārt, ir uzzīmēts ar draw.ellipse
palīdzību. Galarezultāts izskatās sekojoši:
Kā norāda paši modeļa izstrādātāji, tad ‘skatiena identificēšana’ ir vēl eksperimentāla — to parāda arī šis rezultāts, jo abi skatiena punkti nav noteikti pareizi.
Arī pēc cita attēla piemēra (izmantoju Moondream izveidoto rīku HuggingFace platformā) redzams, ka ne visos gadījumos “skatiens” identificēts pareizi:
Apraksts strukturētā formātā
Lai notestētu cik labi modelis spēj sagatavot strukturētu rezultātu, izmantoju citu attēlu. Attēls ir foto uzņēmums no žurnāla “Harvard business review”, kurā attēloti aptaujas rezultāta jautājumi un atbildes. Vispirms ielādēju attēlu un veicu dažas transformācijas:
# Load image #2
url2 = 'https://raw.githubusercontent.com/aivisbr/diff_files/140bf2a6c89ee4f2fc1af43c94c42cd81557440f/20241011_102538.jpg'
response2 = requests.get(url2)
image2 = Image.open(BytesIO(response2.content))
# Resize while maintaining aspect ratio
base_width = 800 # Change this to your desired width
w_percent = base_width / float(image2.width)
h_size = int((float(image2.height) * float(w_percent)))
image2 = image2.resize((base_width, h_size), Image.Resampling.LANCZOS)
# Rotate the image 90 degrees counter-clockwise
image2 = image2.rotate(-90, expand=True) # expand=True to avoid cropping
display(image2) # This works in Google Colab
encoded_image2 = model.encode_image(image2)
Un ar model.query
palīdzību vēlos, lai modelis atgriež strukturētu rezultātu JSON formātā, kurā būtu norādīts aptaujas jautājums, atbilde un skaitliskās vērtības, atsevišķi norādot arī datu avotu:
model.query(encoded_image2, "Extract survey questions, give categories of these questions and amount - how many respondents answered to this question. Return result in a JSON format. In the result include also data source.")["answer"]
Izmēģināju dažādus vaicājuma piemērus, taču galarezultāts ir nepareizs. JSON struktūra ir ievērota, taču vērtības neatbilst attēlā norādītajam:
Citi modeļi (izmantoju ChatGPT 4o) ar šo uzdevumu tiek galā ļoti veiksmīgi:
Iepriekšējie piemēri parāda, ka Moondream 1.9B modelis nav ideāls, taču vienkāršākus uzdevumus tas spēj paveikt.
Rakstā minētais kods ir pieejams GitHub platformā.
Papildu resursi:
- Dokumentācija: https://docs.moondream.ai/quick-start
- Moondream Playground: https://moondream.ai/playground
- Demo rīks HuggingFace platformā: https://huggingface.co/spaces/moondream/gaze-demo
- API risinājums: https://console.moondream.ai/
- Modelis HuggingFace platformā: https://huggingface.co/vikhyatk/moondream2