Переводчик экрана: наложение перевода поверх текста на экране
build / build (push) Failing after 10s
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>
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
<#
|
||||
Превращает частотную таблицу английских биграмм в компактный C#-файл.
|
||||
|
||||
Каждая из 676 биграмм кодируется одним печатным ASCII-символом (квантованный log10
|
||||
вероятности), поэтому вся модель — одна строка примерно в 700 байт вместо мегабайтных
|
||||
словарей и без единой внешней зависимости.
|
||||
|
||||
Источник частот: practicalcryptography.com (корпус английских текстов).
|
||||
#>
|
||||
param(
|
||||
[string]$In = "$PSScriptRoot\english_bigrams.txt",
|
||||
[string]$Out = "$PSScriptRoot\..\src\EnglishBigrams.cs"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$counts = @{}
|
||||
[double]$total = 0
|
||||
foreach ($line in Get-Content $In) {
|
||||
if ($line -notmatch '^\s*([A-Za-z]{2})\s+(\d+)\s*$') { continue }
|
||||
$bg = $Matches[1].ToUpperInvariant()
|
||||
$n = [double]$Matches[2]
|
||||
$counts[$bg] = $n
|
||||
$total += $n
|
||||
}
|
||||
Write-Host "биграмм: $($counts.Count), суммарная частота: $total"
|
||||
|
||||
# Квантование: log10(p) укладываем в печатный ASCII с шагом 1/12 порядка величины.
|
||||
$LO = -9.0; $STEP = 12.0
|
||||
$sb = New-Object Text.StringBuilder
|
||||
$minLog = 0.0
|
||||
foreach ($i in 0..25) {
|
||||
foreach ($j in 0..25) {
|
||||
$bg = [char](65 + $i) + [string][char](65 + $j)
|
||||
$n = if ($counts.ContainsKey($bg)) { $counts[$bg] } else { 0 }
|
||||
# ненаблюдаемой биграмме даём вероятность заметно ниже самой редкой наблюдаемой
|
||||
$p = if ($n -gt 0) { $n / $total } else { 1e-9 }
|
||||
$lg = [Math]::Log10($p)
|
||||
if ($lg -lt $minLog) { $minLog = $lg }
|
||||
$q = [int][Math]::Round(($lg - $LO) * $STEP)
|
||||
if ($q -lt 0) { $q = 0 }
|
||||
if ($q -gt 88) { $q = 88 }
|
||||
[void]$sb.Append([char](34 + $q)) # с 34, чтобы не попасть на кавычку и обратный слэш
|
||||
}
|
||||
}
|
||||
Write-Host "минимальный log10(p): $minLog"
|
||||
|
||||
$data = $sb.ToString()
|
||||
$chunks = @()
|
||||
for ($i = 0; $i -lt $data.Length; $i += 130) {
|
||||
$len = [Math]::Min(130, $data.Length - $i)
|
||||
$chunks += ' "' + $data.Substring($i, $len).Replace('\', '\\') + '"'
|
||||
}
|
||||
$joined = $chunks -join " +`r`n"
|
||||
|
||||
$code = @"
|
||||
using System;
|
||||
|
||||
namespace ScreenTranslator
|
||||
{
|
||||
/// <summary>Модель правдоподобия английского текста по частотам буквенных пар.
|
||||
///
|
||||
/// Решает задачу, с которой не справляются ни словарь, ни алфавит: английская модель OCR,
|
||||
/// прочитав русскую надпись, выдаёт правдоподобное на вид латинское слово («Профиль» →
|
||||
/// «npocbwlb»). Но пары букв выдают подделку сразу — сочетаний «wl», «cb», «bw» в английском
|
||||
/// практически не бывает, тогда как «th», «he», «in» составляют его основу.
|
||||
///
|
||||
/// Таблица — 676 квантованных log10-вероятностей, по символу на пару, около 700 байт.
|
||||
/// Сгенерирована tools/make-bigrams.ps1 из частот корпуса английских текстов
|
||||
/// (practicalcryptography.com). Правится только генератором, не руками.</summary>
|
||||
internal static class EnglishBigrams
|
||||
{
|
||||
const double Lo = $LO, Step = $STEP;
|
||||
|
||||
const string Table =
|
||||
$joined;
|
||||
|
||||
/// <summary>Средний log10 вероятности пар букв в слове. Около -2,5 у обычного английского
|
||||
/// слова, ниже -4 у случайного набора букв. NaN, если букв слишком мало для оценки.</summary>
|
||||
public static double WordScore(string word)
|
||||
{
|
||||
if (string.IsNullOrEmpty(word)) return double.NaN;
|
||||
double sum = 0; int n = 0;
|
||||
for (int i = 0; i + 1 < word.Length; i++)
|
||||
{
|
||||
int a = Index(word[i]), b = Index(word[i + 1]);
|
||||
if (a < 0 || b < 0) continue;
|
||||
sum += Lo + (Table[a * 26 + b] - 34) / Step;
|
||||
n++;
|
||||
}
|
||||
return n == 0 ? double.NaN : sum / n;
|
||||
}
|
||||
|
||||
static int Index(char c)
|
||||
{
|
||||
if (c >= 'a' && c <= 'z') return c - 'a';
|
||||
if (c >= 'A' && c <= 'Z') return c - 'A';
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <summary>Оценка текста целиком: усреднение по словам длиной от четырёх букв —
|
||||
/// на более коротких пары букв ничего не доказывают. NaN, если оценивать нечего.</summary>
|
||||
public static double TextScore(string text)
|
||||
{
|
||||
if (string.IsNullOrEmpty(text)) return double.NaN;
|
||||
double sum = 0; int n = 0;
|
||||
foreach (var w in text.Split(new[] { ' ', '\t', '\n', '\r', '.', ',', ':', ';', '!', '?',
|
||||
'(', ')', '[', ']', '"', '\'', '/', '\\', '-' },
|
||||
StringSplitOptions.RemoveEmptyEntries))
|
||||
{
|
||||
if (w.Length < 4) continue;
|
||||
double s = WordScore(w);
|
||||
if (double.IsNaN(s)) continue;
|
||||
sum += s; n++;
|
||||
}
|
||||
return n == 0 ? double.NaN : sum / n;
|
||||
}
|
||||
}
|
||||
}
|
||||
"@
|
||||
|
||||
$code | Set-Content $Out -Encoding UTF8
|
||||
Write-Host "записано: $Out ($($data.Length) символов таблицы)"
|
||||
Reference in New Issue
Block a user