DEV Community

xiaoru chen
xiaoru chen

Posted on

I Missed a Watermark in My Own Photo. A Vision Model Read It Out Loud.

The corner I never looked at

I curated a batch of event photos for a colleague last week. Before sending them over I did my own pass: composition, exposure, stray bystanders, anything embarrassing in the background. All clear, so I hit send.

The next morning: "what's that line of text in the bottom-right corner?"

At 300% zoom there it was. A light-gray copyright line, small enough that I had looked straight at that corner several times and registered nothing. Human vision is blind to the unexpected periphery — you look at the subject, and the corners go dark. The annoying part wasn't the miss. It was realizing my "check" had never covered that class of thing at all.

So I re-ran the batch through the vision describe command in the Bailian CLI (bl), asking it what text was on screen. It read out every line, including the watermark I missed. Then I did the thing I should always do before trusting a model output that flatters me: I tried to make it fail.

Test image with a hidden light-gray watermark in the bottom-right corner

One image proves nothing

Here's the failure mode that actually matters with vision models. It isn't blur, and it isn't misreading a character. It's answering about an image it never really inspected — producing a plausible-sounding description because the question implied one.

Ask "is there a watermark in this photo?" and a model that wants to be helpful has a cheap path to an answer. If I'd stopped at that one run, I'd have had a screenshot showing it found my watermark, and zero evidence it wasn't just agreeing with me.

That's why the test needs a control. Same media, one variable changed, same question both times. If the model reads the mark when it's there and says "nothing in the corners" when it isn't, it's looking. If it says "yes, watermark" both times, it's guessing.

Building the controls

I made my own media so I'd know the ground truth exactly:

  • One 900×600 image with an 18px light-gray line tucked into the bottom-right corner, and the identical image with that line removed
  • One four-second clip with a white label burned into the bottom-right corner, and the same clip rendered without it

Nothing about these is subtle by accident. The point of a control isn't to find the model's breaking point — it's to establish that it reports presence and absence differently. Difficulty comes later.

Four runs, four correct verdicts:

Single-variable test matrix: image with mark, image without, video with label, video without — 4 out of 4 correct

With the mark, it read the headline and the tiny corner line, character for character. Without it, the answer named only the headline and stated plainly that the corners held no watermark and no small text. Same split on video: label present, read aloud; label removed, "no text or watermark at all," followed by a straightforward description of what was on screen in each time range.

Runs two and four are the ones carrying the weight. A model that fabricates agreement dies there, and this one didn't.

The command is one line

bl vision describe --image ./press-photo.jpg --prompt "What text is in the image, including tiny corner text?"
Enter fullscreen mode Exit fullscreen mode

Local paths go straight in; the CLI handles the upload, so there's no detour through a file host to get a public URL first. Video swaps one flag:

bl vision describe --video ./clip.mp4 --prompt "What text appears in the video?"
Enter fullscreen mode Exit fullscreen mode

Real terminal output: the model reading the corner watermark out of the test image

--image or --video depending on the media type, and --prompt has a working default if you omit it. I still write the question out every time. Vague questions get scene descriptions; specific questions get the inventory you actually wanted. "What text is on screen, including the corners" and "describe this image" return genuinely different documents.

The default model is qwen3-vl-plus. You can point --model at another vision model, but my controls only cover the default — if you switch, re-run the matrix. That's four commands, and it's the only way the result means anything for your setup.

Real terminal output: video label read correctly, and the control run reporting no text at all

What I use it for now

Watermark checking was the entry point, not the use case. Once the pattern worked I started pointing it at other things where eyes skip details:

Reuse clearance. Before publishing someone else's image or clip, ask what text and marks are on screen. Corner watermarks, channel bugs, attribution lines, photographer handles — one pass surfaces all of them, and the answer is text you can keep next to the asset instead of a memory of having checked.

Archive mining. A folder of training recordings or event replays becomes searchable by running "what text appears on screen" across it and filing by what comes back. Titles and lower-thirds get read out without scrubbing a single timeline.

Document scan double-check. Stamps, page numbers, reference codes in the margins of scanned contracts and reports. Have them read aloud, then compare against the body text. The machine is good at "here is everything printed on this page," which is exactly the tedious part.

Accessibility descriptions. Describing what an infographic literally says for a colleague who can't see it. Not a summary of the vibe — the actual text, in place.

All four are the same capability: marks on screen get missed by eyes and caught by a per-region scan.

Where it stops helping

My proof covers watermark-sized text. The line I hid was 18px light gray and it read it correctly. Smaller, fainter, or lower-contrast against the background — no promise. Run your own controls on your own media before you rely on it.

Video is understood per frame and per time range, which the output makes obvious: it tells you what was on screen "from 0.2s to 3s." That's sampling, not a continuous stream. My label sat in the corner for the whole four seconds, so it was never at risk. A half-second flash is a different question, and I haven't tested it.

And it reads what's there, not what's true. A typo in the image gets read aloud faithfully, and a copyright line being legible tells you nothing about whether the license is in order. First pass, not final pass — the last pair of eyes stays human.

Run it yourself

npm install -g bailian-cli
bl vision describe --image your-photo.jpg --prompt "What text is in the image, including tiny corner text?"
Enter fullscreen mode Exit fullscreen mode

Keys come from the console API key page (region: China Beijing). Pick a photo you've looked at a dozen times without really inspecting, ask what text is on it, and see whether the answer includes something you missed. Mine did.

What's the smallest text a vision model has correctly caught for you — and did you test the negative case, or take the first flattering answer?

Top comments (0)