Texterkennung

Dienste zur Texterkennung (OCR-Services) ermöglichen die Extraktion von Text aus Bildern. Dadurch kann ein Bild innerhalb der Bewertungssektion eines Bild Lesen Testschrittes mit einer Zeichenkette verglichen werden. Über die Multimedia API können zudem einzelne Textpassagen in einem Bild erkannt werden.

Standardmäßig unterstützt ecu.test eine Texterkennung mittels der Tesseract OCR-Engine. Es ist möglich, weitere Dienste zur Texterkennung als benutzerdefinierte OCR-Services mittels UserPyModules zu integrieren. Diese können in der Testkonfiguration in der Registerkarte Medienzugriff konfiguriert werden.

Benutzerdefinierte Texterkennung

Die Texterkennung in ecu.test benutzt einen OCR-Service, um entweder ein Bild in eine Zeichenkette zu konvertieren (z.B. durch Aneinanderreihen des erkannten Textes in lexikografischer Reihenfolge), oder einzelne Textpassagen innerhalb des Bildes zu identifizieren. Neben des bereits vorhandenen Dienstes von Tesseract können Anwender ecu.test mit einer eigenen Variante erweitern. Dazu muss ein Pythonmodul zur Verfügung gestellt werden. Damit dieses als gültiger OCR-Service erkannt wird, sind mehrere Anforderungen zu erfüllen:

  • Das Modul muss sich im Ordner UserPyModules des aktuellen Workspaces befinden.

  • Auf oberster Ebene des Skripts muss die Variable MODULE_TYPE = ‚UserOCRService‘ definiert sein.

  • Es muss eine OCR-Service Klasse implementiert werden, welche das folgende Interface erfüllt:

    • __init__

    • GetServiceId

    • ImageToText

    • FindTextPositions

    • GetAvailableParameters

    • GetParameter

    • SetParamter

  • Es dürfen keine zwei OCR-Services mit der gleichen Service-Id existieren.

Weitere Details zum Interface können dem Beispiel im folgenden Abschnitt entnommen werden.

Neu hinzugefügte bzw. modifizierte OCR-Services werden von ecu.test erkannt, nachdem die Benutzerbibliotheken aktualisiert worden sind. Dies geschieht im Hauptmenu „Extras“.

Um einen benutzerdefinierten OCR-Service mittels der internen Multimedia API zu benutzen, kann eine Instanz per

  • api.Multimedia.GetOCRService oder

  • api.Multimedia.GetDefaultOCRService

erzeugt werden. Die implementierten Methoden des Interfaces können direkt auf dem zurückgegebenen Objekt aufgerufen werden, z.B. api.Multimedia.GetOCRService('ExampleOCR').GetParamter('threshold').

Minimalbeispiel zur Texterkennung

Eine Beispielimplementierung eines benutzerdefinierten OCR-Services kann im ecu.test-Installationsverzeichnis gefunden werden:

  • Templates/DefaultData/UserPyModules/UserOCRService/ExampleOCRService.py

Das Minimalbeispiel in ExampleOCRService.py beinhaltet den folgenden Code. Weitere Informationen zu den einzelnen Methoden können den Doc-Strings entnommen werden.

from __future__ import annotations
import logging
from typing import Any, TYPE_CHECKING

MODULE_TYPE = 'OCRService'

if TYPE_CHECKING:
    import numpy.typing as npt


class ExampleOCR:

    def __init__(self):
        """
        The service can be created when *GetOCRService(serviceId)* is called on the internal
        multimedia API, where *serviceId* is the identifier returned by *GetServiceId()*.
        Further, if the service is selected in the test configuration's media access tab,
        the class is instantiated
        - when an image is compared to a string during evaluation in image read and
        - when GetDefaultOCRService() is called on the Multimedia API via
            *api.Multimedia.GetDefaultOCRService()*.
        """
        self.ocrMode = 'dummy'
        self.canDetectText = False
        self.threshold = 0.4

    @staticmethod
    def GetServiceId() -> str:
        """
        The identifier of the OCR service as displayed in the test configuration's media access tab
        and used to create service via the multimedia api (see __init__).

        :return: Identifier of the service
        """
        return 'ExampleOCR'

    def ImageToText(self, img: npt.NDArray) -> str:
        """
        Converts the image to a string representation, e.g. by concatenating all found text in
        lexicographic order.

        :param img: Numpy image (width x height x channels)
        :return: String representation of the image
        """
        height, width = img.shape[:2]
        return f'The image has dimension ({height}, {width}) and maybe some text.'

    def FindTextPositions(self, img: npt.NDArray) -> list[dict[str, int | float | str]]:
        """
        Returns a list of all text matches found in the image.
        Each text match is a dictionary that must contain information about
        - its location in terms of a bounding box with upper left corner (x,y) and
          dimensions (width, height),
        - its content (text) and
        - the confidence of the individual match (confidence).

        :param img: Numpy image (width x height x channels)
        :return: List of dictionaries describing all text matches
        """
        h, w, __ = img.shape
        return [
            {
                'x': int(w / 4.0),
                'y': int(h / 4.0),
                'width': int(w / 2),
                'height': int(h / 2),
                'text': 'Some large imaginary text in the middle of the image.',
                'confidence': 0.42,
            }
        ]

    def GetAvailableParameters(self) -> list[dict[str, Any]]:
        """
        Returns a list of all configureable parameters. Each parameter must be described by a unique
        name and a default value. A short description is optional and will be displayed in a
        configuration dialog (tcf, media access tab).

        :return: List of all available paramters
        """
        return [
            {
                'name': 'ocrMode',
                'default': 'dummy',
                'description': 'This text describes what can or cannot be done.'
            },
            {
                'name': 'canDetectText',
                'default': False
            },
            {
                'name': 'threshold',
                'default': 0.5,
                'description': 'Matches with confidence below this value are ignored.'
            },
        ]

    def GetParameter(self, name: str) -> Any:
        """
        Returns the value of the parameter addressed by name.

        :param name: Name of the parameter
        :return: Value of parameter addressed by name
        """
        try:
            return getattr(self, name)
        except AttributeError:
            logging.getLogger('ecu.test').info(
                '{}: No such parameter: {}'.format(self.GetServiceId(), name)
            )

    def SetParameter(self, name: str, value: Any) -> None:
        """
        Sets the value of the parameter addressed by name.

        :param name: Name of the parameter
        :param value: Value of the parameter
        """
        if hasattr(self, name):
            setattr(self, name, value)
        else:
            logging.getLogger('ecu.test').info(
                '{}: No such parameter: {}'.format(self.GetServiceId(), name)
            )