Skip to main content
Use this guide when you need to know where something is in an image, not only whether it is there: to count items, crop them out, check a shelf or a workbench, or tell an agent where to click.

What you’re building

A workshop bench with purple boxes around three screwdrivers and purple dots on two hammers

Boxes around every screwdriver and a point on each hammer, from two requests to Moondream 3.1. Each request took under two seconds.

Moondream models answer two slash commands in an ordinary chat completions request. Start the text of a user message with /detect {"object":"screwdriver"} and the model returns a bounding box for every match. Start it with /point {"object":"hammer"} and it returns one point per match. Both come back as JSON in message.content, with coordinates from 0 to 1 relative to the image, so you need no detection API, schema, or prompt engineering. By the end, you will have a script that:
  1. Asks for bounding boxes around one kind of object.
  2. Asks for a point on each instance of another.
  3. Draws the boxes and points on the photo and saves it as a PNG.
Building with a coding agent? Paste the URL of this page into Claude Code, Codex, or Cursor and ask it to highlight the objects a user types in over the photo they upload, using this recipe.

Before you start

You need:
  • An OpenRouter API key available as OPENROUTER_API_KEY
  • Bun, or Node.js 22.18 or newer (both run TypeScript files directly)
  • sharp to load and draw on the image: npm install sharp
  • A JPEG, PNG, WebP, or GIF photo. To follow along exactly, download the workbench photo used on this page and save it as workbench.jpg.
This guide uses moondream/moondream3.1-9b-a2b. moondream/moondream3-preview takes the same commands. See the Moondream 3.1 model page for pricing, and Image Understanding for the ways to send images.

Step 1: Find every match with /detect

Create find-objects.ts. The loadImage helper turns the photo upright and reads its format: Moondream reads the stored pixels and ignores the EXIF orientation tag that phone cameras set, so a phone photo would otherwise come back with coordinates for a sideways image. The locate helper sends one image and one command, and returns the array of matches. The command must be the first thing in the text part, and its argument must be a JSON object with an object key.
Run it with bun find-objects.ts or node find-objects.ts:
Each box is the top-left (x_min, y_min) and bottom-right (x_max, y_max) corner as a fraction of the image width and height, measured from the top-left of the image. When nothing matches, the model returns {"objects": []}. The object value can be a short description as well as a noun. {"object":"red-handled screwdriver"} returns one box, around the red one only.

Step 2: Mark each match with /point

/point returns one point on each match instead of a box, which is all you need to count things or to pass a click target to an agent. Add these lines to the end of the script:

Step 3: Draw the results on the image

Multiply the fractions by the image size to get pixels, build an SVG with a rectangle per box and a circle per point, and let sharp lay it over the photo. Add this to the end of the script:
Open annotated.png to see the image at the top of this page. To crop a match out instead of drawing it, pass the same pixel values to sharp(image.data).extract({ left: x, top: y, width: w, height: h }), rounded to whole numbers.

Troubleshooting

Moondream returns the same error for every malformed command: Provider returned error, with "message":"Invalid request." inside error.metadata.raw.

Check your work

The script should print three boxes for the screwdrivers and two points for the hammers, then save annotated.png with a box around each screwdriver and a dot on each hammer. Point it at your own photo with bun find-objects.ts my-photo.jpg and change the two object strings to what you want to find. Small, tightly packed items can be missed, so check the count against the image before you rely on it.