Files
screen-translator/tools/make-bigrams.ps1
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

124 lines
5.7 KiB
PowerShell
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.
<#
Превращает частотную таблицу английских биграмм в компактный 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) символов таблицы)"