Files
screen-translator/README.md
T
gusadmin 7c6b182402
build / build (push) Failing after 10s
Переводчик экрана: наложение перевода поверх текста на экране
Программа накладывает русский перевод прямо на английские надписи —
как переводчик по фото, только для монитора. Работает с играми,
интерфейсами и документами: распознаёт кадр, собирает текст в связные
блоки, переводит и рисует плашки под цвет фона.

Распознавание — встроенный Windows.Media.Ocr, офлайн. Перевод — цепочка
движков с автопереключением (google, lingva, mymemory), потому что до
разных сервисов из разных сетей достучаться получается по-разному.

Собирается без .NET SDK: Roslyn из Build Tools плюс сборки Framework 4.8,
которые уже есть в системе. На выходе один exe без установщика.

Главная сложность оказалась не в переводе, а в том, чтобы понять, где на
экране связный текст, а где отдельные надписи. Классические алгоритмы
сегментации решают это по межстрочному зазору, но в плотном интерфейсе он
одинаков у строк абзаца и у соседних контролов. Поэтому решение принимает
ансамбль признаков: фон, граница между строками, заполненность строки,
кегль. Надписи на чужом языке отсеиваются тремя уровнями — форма слова,
вторая модель распознавания и частоты буквенных пар.

Чтобы правки эвристик не оценивались на глаз, в tools лежат эталонные
кадры и RenderTest: прогон конвейера без окна с подсчётом попаданий.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 15:54:02 +03:00

242 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Переводчик экрана
Наводит перевод поверх английского текста прямо на экране — как Яндекс.Переводчик по фото,
только для монитора. Нажал горячую клавишу → английские надписи закрылись плашками
под цвет фона, на них русский текст. Остальная картинка остаётся на месте.
Работает с любой программой: игры, PDF, видео, интерфейсы, картинки — всё, что видно на экране.
* Распознавание — встроенный в Windows OCR: офлайн, бесплатно, ничего ставить не надо.
* Перевод — несколько движков с автоматическим переключением, если какой-то не отвечает.
* Один файл `ScreenTranslator.exe` (81 КБ), без установщика и без .NET SDK.
## Как пользоваться
| Клавиши | Действие |
|---|---|
| `Ctrl+Alt+Z` | перевести монитор, на котором сейчас курсор |
| `Ctrl+Alt+X` | выделить область рамкой и перевести её |
| `Esc` или повторное нажатие | закрыть перевод |
| Пробел (удерживать) | подсмотреть оригинал под плашками |
| Наведение мышью | показать оригинал одного фрагмента |
| Клик по плашке | скопировать её перевод |
| `Ctrl+C` | скопировать весь перевод |
Значок в трее — те же команды плюс «Проверить переводчики» и правка настроек.
Пока идёт распознавание и перевод, экран уже заморожен, а фрагменты подсвечены — видно,
что программа работает. Полный цикл на мониторе 3440×1440 — около трёх секунд
(0,6 с распознавание + ~2 с перевод), повтор того же текста — мгновенно из кэша.
## Установка
Скопировать папку `dist` куда угодно и запустить `ScreenTranslator.exe`. Всё.
Если Windows покажет «SmartScreen защитил ваш компьютер» — «Подробнее» → «Выполнить в любом случае»
(файл без цифровой подписи).
Автозапуск: `Win+R``shell:startup` → положить туда ярлык на exe.
### Требование одно: модель распознавания
Нужен установленный в Windows языковой компонент «Распознавание текста» для английского.
Проверить: значок в трее → «Проверить переводчики» покажет ошибку, если моделей нет.
Ставится так: **Параметры → Время и язык → Язык и регион** → у английского языка `···`
**Компоненты языка** → отметить **Распознавание текста**. На большинстве систем уже стоит.
## Настройка
Рядом с exe лежит `config.json` (создаётся при первом запуске). После правки —
«Перечитать настройки» в трее, перезапуск не нужен.
Самое полезное:
```jsonc
"hotkeyTranslate": "Ctrl+Alt+Z", // своя комбинация, если эта занята
"ocrLanguage": "en-US", // язык оригинала на экране
"targetLang": "ru", // на какой переводить
"overlayMode": "frozen", // frozen — стоп-кадр; live — плашки поверх живого экрана
"captureScope": "monitor", // monitor — экран под курсором; all — все мониторы сразу
"ocrScale": 2.0, // 1.0 быстрее, 2.0 точнее на мелком шрифте
"detectForeign": true, // не трогать надписи на языке системы (см. ниже)
"markTranslated": true, // подчёркивать переведённые места
"chain": ["google", "lingva", "mymemory"]
```
### Смешанный экран
Английская модель OCR, встретив русскую надпись, читает её как бессмыслицу вроде `C03AaTb`
или `Pa3Mep` — и без защиты программа переводила бы эту кашу, залепляя плашками весь
русский интерфейс.
Защита двухступенчатая:
1. `skipGarbage` — отсев по форме слов: цифра посреди слова, заглавная после строчной,
символы-артефакты, имена файлов.
2. `detectForeign` — кадр дополнительно распознаётся моделью второго языка. Если она видит
на этом месте кириллицу, надпись изначально русская и её не трогают. Это ещё примерно
столько же времени на распознавание; выключается, если весь экран и так на одном языке.
3. Список ходовых английских слов ([EnglishWords.cs](src/EnglishWords.cs)) — арбитр в спорных
случаях. Русская модель читает латинское «Cancel» как «Сапсеl», то есть по алфавиту оно
неотличимо от русского «Профиль» → `npocbwlb`. Если основная модель выдала узнаваемое
английское слово, верим ей.
На эталонах это даёт 22 распознанных блока из 22 на английском кадре (ни одной потери)
и 2 оставшихся из 20 на русском.
Совсем идеально не будет: очень мелкие надписи (вкладки браузера, 8 px) обе модели читают
плохо, и часть их может проскочить. **Если на экране мешанина языков — надёжнее `Ctrl+Alt+X`
и рамка вокруг нужного куска.**
### Чего программа не умеет и не сможет
Рабочий стол, панель задач, списки установленных программ — не её сценарий. Подписи там это
названия приложений, а отличить название от обычного слова невозможно: «Steam», «Discord»,
«Wand» одновременно и то, и другое. Перевод получится буквальным и бессмысленным.
Помогает `"minWords": 2` — тогда переводятся только фразы от двух слов, а одиночные подписи
остаются как есть. Но за это приходится платить: на интерфейсе игры теряется половина надписей
(«Mining», «Search», «Consumable» — тоже одиночные слова), поэтому по умолчанию стоит `1`.
### Про переводчики и Россию
`chain` — это порядок попыток. Первый ответивший движок используется, упавший временно
исключается и пробуется следующий. Ничего перенастраивать при переезде в другую сеть не нужно —
если Google недоступен, программа сама уйдёт на запасной.
Что стоит знать:
* **google** — бесплатный публичный эндпоинт, без ключа, лучшее соотношение качества и скорости.
`translate.googleapis.com` в России не заблокирован (замедляли YouTube, а не API), но
у отдельных провайдеров ТСПУ рвёт к нему TLS. Отсюда и нужна цепочка.
* **lingva** — открытый шлюз к тому же Google на других адресах. Ровно тот случай, когда
напрямую не пускает, а через шлюз проходит. Медленнее (2–4 с).
* **mymemory** — последний рубеж, без ключа, лимит около 5000 слов в сутки на IP.
* **yandex** — самый устойчивый канал внутри РФ, но нужен платный Yandex Cloud
(`folderId` + API-ключ).
* **deepl** — лучшее качество en→ru, есть бесплатный тариф с ключом.
* **llm** — перевод целым экраном через Claude API: лучше держит контекст интерфейса,
но платный по токенам и медленнее.
* **libretranslate** / **relay** — свой сервер, ничего не уходит наружу.
Проверить, что доступно из конкретной сети: трей → **Проверить переводчики** — покажет
время ответа или ошибку по каждому.
Через прокси (например, свой sing-box в режиме mixed):
```jsonc
"proxy": "http://192.168.5.42:2080"
```
### Свой релей
Если публичные сервисы недоступны, а свой сервер есть — поднимите эндпоинт, принимающий
`POST {"texts": [...], "from": "en", "to": "ru"}` и возвращающий `{"texts": [...]}`
той же длины, затем:
```jsonc
"chain": ["relay", "google"],
"engines": { "relay": { "url": "https://ваш-домен/translate" } }
```
Внутри релей может ходить хоть в Google, хоть в локальную модель — клиенту всё равно.
## Приватность
Распознавание идёт локально, наружу уходит **только распознанный текст** — картинка экрана
никогда не покидает компьютер. Но текст с экрана всё же уходит в выбранный сервис перевода,
поэтому для чувствительного содержимого используйте `libretranslate` или `relay` на своём сервере.
Кэш переводов лежит рядом в `cache.json` — его можно удалять в любой момент.
## Сборка из исходников
.NET SDK не нужен — используется Roslyn из Visual Studio Build Tools и сборки .NET Framework 4.8,
которые уже есть в Windows.
```powershell
.\build.ps1 # собрать в .\dist
.\build.ps1 -Run # собрать и запустить
.\build.ps1 -Test # плюс RenderTest.exe
```
`RenderTest.exe` — не сама программа, а диагностика: прогоняет весь конвейер без окна и
сохраняет две картинки (как OCR разметил блоки и как лёг перевод). Запускать лучше из терминала;
при двойном клике он всё равно отработает и подождёт Enter, а картинки положит
в `%TEMP%\screen-translator-test`.
```powershell
.\dist\RenderTest.exe --in кадр.png --out .\dist\test
.\dist\RenderTest.exe --scale 1.0 --no-translate
```
Два эталонных кадра для прогонов рисуются скриптами:
* `tools\make-sample.ps1` — англоязычный интерфейс. Проверка «всё ли переводится»:
правильный результат — 22 блока.
* `tools\make-sample-ru.ps1` — русский интерфейс. Негативная проверка: английская модель OCR
читает кириллицу как кашу, и её нельзя ни переводить, ни закрывать плашками.
Правильный результат — около 5 блоков (мелкие обрывки вроде «Tn»), было 20 до фильтра.
Оба стоит прогонять после правок в `Blocks.cs`: ужесточение фильтра легко начинает резать
короткие английские кнопки, и английский кадр это сразу показывает.
Флаг `--expect файл.txt` считает, сколько заранее известных строк реально распозналось —
без такой метрики улучшение и ухудшение на глаз выглядят одинаково убедительно.
`--selftest` проверяет биграммную модель на примерах настоящих надписей и каши.
```powershell
.\dist\RenderTest.exe --in wow.jpg --expect tools\expect-wow.txt --no-translate
.\dist\RenderTest.exe --selftest
```
Текущие показатели (после каждой правки должны держаться):
| Кадр | Метрика | Значение |
|---|---|---|
| Скриншот игры | распознано известных надписей | 14 из 14 |
| Английский эталон | ложно отброшено | 0 |
| Русский эталон | осталось непойманной каши | 2 из 20 |
## Что внутри
| Файл | Зачем |
|---|---|
| `src/Program.cs` | трей, горячие клавиши, порядок работы |
| `src/ScreenCapture.cs` | снимок экрана и отдельная копия пикселей для фоновой обработки |
| `src/Ocr.cs` | Windows.Media.Ocr, увеличение кадра ради мелкого шрифта |
| `src/Blocks.cs` | склейка строк в абзацы, отсев каши, подбор цветов плашки |
| `src/Translators.cs` | движки перевода, цепочка фоллбеков, кэш |
| `src/PlateRenderer.cs` | подбор кегля и отрисовка плашки |
| `src/OverlayForm.cs` | окно поверх экрана |
| `src/RegionSelector.cs` | выделение области рамкой |
| `src/EnglishBigrams.cs` | частоты буквенных пар: отличает слово от каши распознавания |
| `src/EnglishWords.cs` | список ходовых английских слов — арбитр в спорных случаях |
Откуда взялись решения (короткий разбор чужого опыта):
* Классические алгоритмы сегментации — RLSA, Docstrum, XY-cut, Voronoi — принимают решение
«склеивать или нет» по одному сигналу, межстрочному зазору. В плотном интерфейсе он
одинаков у строк абзаца и у соседних контролов, поэтому ни один из них эту задачу не решает.
Работает только ансамбль независимых признаков — как в Tesseract (`paragraphs.h`, геометрия
плюс пунктуация) и UIED, где графику детектируют отдельно от текста.
* Отсюда четыре признака разрыва: разный фон, граница между строками, «строка не дотянула
до правого края» (в абзаце короткой бывает только последняя) и несовпадение кегля.
* Определение чужого языка — это не задача language identification: готовые детекторы
(cld3, fastText) всегда выбирают какой-то язык из списка и уверенно зовут кашу английским.
Нужна одноклассовая модель «похоже на настоящий английский», отсюда биграммы.
* Проверено и отвергнуто: перевод в оттенки серого и инверсия тёмного фона перед OCR.
Обещали заметный прирост, на замере не дали ничего (14 из 20 во всех вариантах),
поэтому по умолчанию выключены, хотя в конфиге остались.
Тонкие места, если будете править:
* Приложение помечено `PerMonitorV2` — все координаты физические, иначе разметка OCR
разъезжается с экраном при масштабе интерфейса больше 100 %.
* OCR обводит только тело букв, без выносных элементов, поэтому плашка расширяется
по вертикали — иначе сверху и снизу торчат хвосты оригинала.
* Строки склеиваются в абзац по межстрочному интервалу и одинаковому кеглю: рваные строки
переводятся заметно хуже целого абзаца.