Usługi programistyczne

Ten sam kod, dwa różne światy

Projekt aplikacji mobilnej OCR · Oprogramowanie / OCR

Zdjęcie ekranu

Wyzwanie

Każdy, kto próbował zmusić komputer do odczytania tekstu ze zdjęcia, wie, że OCR to często droga przez mękę. A jeśli to zdjęcie ekranu monitora? Wtedy wchodzimy na nowy poziom: odblaski, wzory moiré, nieostrość z fotografowania siatki pikseli.

W jednym z projektów miałam zbudować w Kotlinie rdzeń aplikacji mobilnej, której kluczową funkcją było precyzyjne odczytanie treści ze zrzutu ekranu. Bez solidnego „czyszczenia” obrazu cała funkcja byłaby bezużyteczna – silnik OCR zwracałby cyfrową sałatkę zamiast tekstu.

Co zrobiłam

Szybko odkryłam, że największym problemem nie są same zakłócenia, ale ich zmienność. Kluczowa okazała się różnica między trybem jasnym a ciemnym: filtry świetne dla czarnego tekstu na białym tle kompletnie zawodziły przy białym tekście na ciemnym. Jeden uniwersalny zestaw ustawień byłby kompromisem słabym w obu przypadkach. Rozwiązanie musiało być mądrzejsze – adaptacyjne.

Zamiast narzucać jedno rozwiązanie siłą, nauczyłam kod najpierw analizować problem. Funkcja sprawdza średnią jasność obrazu i na tej podstawie rozpoznaje tryb jasny albo ciemny. Dopiero wtedy dobiera zupełnie inny zestaw narzędzi z OpenCV – inny tryb progowania i inne wartości usuwania szumu. Jak lekarz, który najpierw stawia diagnozę, a potem dobiera leki:

fun preprocessForTesseract(srcMat: Mat): Mat? {
    try {
        val grayMat = Mat()
        Imgproc.cvtColor(srcMat, grayMat, Imgproc.COLOR_BGR2GRAY)
        // Kluczowy krok: średnia jasność obrazu wykrywa tryb
        val avgBrightness = Core.mean(grayMat).`val`[0]
        val isDarkBackground = avgBrightness < 100 // próg dla dark mode
        val thresholded = Mat()
        Imgproc.adaptiveThreshold(
            grayMat, thresholded, 255.0,
            Imgproc.ADAPTIVE_THRESH_MEAN_C,
            // dla ciemnego tła odwracam działanie filtra
            if (isDarkBackground) Imgproc.THRESH_BINARY_INV else Imgproc.THRESH_BINARY,
            if (isDarkBackground) 31 else 45,
            if (isDarkBackground) 11.0 else 15.0
        )
        grayMat.release()
        return thresholded
    } catch (e: Exception) { return null }
}

Efekt

Adaptacyjna logika przyniosła natychmiastowe efekty. Dokładność OCR wzrosła diametralnie – aplikacja radziła sobie równie dobrze ze zdjęciem Notatnika w Windowsie, jak i z terminalem programisty na ciemnym tle. Zawodna funkcja stała się stabilnym, godnym zaufania komponentem – fundamentem, na którym można było spokojnie rozwijać całą aplikację.

Uwielbiam takie wyzwania, bo w programowaniu diabeł tkwi w szczegółach. Zamiast szukać drogi na skróty, wolę zrozumieć problem do końca i zbudować rozwiązanie, które sprawdzi się w realnym świecie.

To jedna strona projektu aplikacji OCR. O drugiej – skrypcie w Pythonie, który zasilał jej bazę danych – pisałam tutaj.

Wróć do realizacji

Masz projekt do omówienia?

Napisz, o co chodzi - nie musi być technicznie, od tego jestem ja. Odezwę się i powiem wprost, czy i jak mogę pomóc.