Der Name ist das am wenigsten vorhersehbare Feld einer maschinenlesbaren Zone. Die Daten bestehen aus sechs Ziffern, die Dokumentnummer aus neun Zeichen mit Prüfziffer, und die Codes stammen aus einer festen Liste. Der Name ist das, wie ein Mensch eben heißt, zusammengepresst in eine feste Anzahl von Großbuchstaben, ohne Leerzeichen, ohne Akzente, ohne Satzzeichen und ohne eine Prüfziffer, die Ihnen sagt, wenn ein Zeichen falsch gelesen wurde.

Dieser Beitrag zeigt, wie die Zone einen Namen schreibt, wie man ihn wieder in Nachname und Vornamen zerlegt, was die Transliteration mit ihm macht, woran man erkennt, dass er gekürzt sein könnte, und wie man ihn mit dem auf der Seite gedruckten Namen vergleicht. Jeder Name unten ist erfunden oder ist das ICAO-eigene Muster, Anna Maria Eriksson; keine reale Person kommt vor.

Dies ist der Blog von doc.cheap, einer OCR-API für Reisepässe und Ausweise. Für alles Folgende brauchen Sie sie nicht; der Code hat keine Abhängigkeiten.

Wo der Name steht

ICAO Doc 9303, der Standard für maschinenlesbare Reisedokumente, gibt dem Namen in jedem seiner drei Formate ein Feld fester Breite. Die Positionen zählen hier ab 1, so wie der Standard sie zählt:

Format Wo der Name steht Breite
TD3 (Reisepässe), 2 × 44 Zeile 1, Positionen 6–44 39 Zeichen
TD2, 2 × 36 Zeile 1, Positionen 6–36 31 Zeichen
TD1 (Ausweiskarten), 3 × 30 Zeile 3, die ganze Zeile 30 Zeichen

Die ersten fünf Zeichen von Zeile 1 bei TD3 oder TD2 sind der Dokumentcode und der ausstellende Staat; der Name nimmt alles danach ein. TD1 gibt dem Namen eine eigene Zeile, und trotzdem ist es das schmalste der drei Felder. Die Seite zu den MRZ-Formaten geht jedes Format Feld für Feld durch.

Im TD3-Muster lautet Zeile 1 P<UTOERIKSSON<<ANNA<MARIA<<<<<<<<<<<<<<<<<<<. Ohne P<UTO ist das Namensfeld ERIKSSON<<ANNA<MARIA, gefolgt von Füllzeichen bis 39 Zeichen.

Die Grammatik: <<, < und das Auffüllen

Das Alphabet der Zone ist A–Z, 0–9 und das Füllzeichen <. Ein Name nutzt darauf drei Konventionen:

  • Der primäre Identifikator steht vorn. Das ist meist der Nachname.
  • Zwei Füllzeichen, <<, beenden den primären Identifikator. Was folgt, ist der sekundäre Identifikator, meist die Vornamen.
  • Ein Füllzeichen, <, trennt Wörter innerhalb eines der beiden Teile. ANNA<MARIA sind zwei Vornamen; VAN<DER<BERG ist ein Nachname aus drei Wörtern.

Was vom Feld nach dem Namen übrig bleibt, wird mit < aufgefüllt. Einen Namen zurückzulesen sind also drei Schritte: die Füllzeichen am Ende entfernen, am ersten << trennen und die verbleibenden einzelnen < in Leerzeichen umwandeln.

Zwei Formen sehen seltsam aus und sind keine Fehler. Eine Person mit nur einem Namen hat nur einen primären Identifikator, das Feld besteht also aus dem Namen und dann Füllzeichen, ohne << davor: OKONKWO<<<<…. Ein Feld mit Trennzeichen und nichts dahinter, NAKAMURA<<<<…, liest sich genau gleich, sobald die Füllzeichen am Ende entfernt sind. In beiden Fällen ist der sekundäre Identifikator leer, nicht fehlend.

Was nicht überlebt, sind Satzzeichen und das Leerzeichen selbst. Ein Bindestrich, ein Apostroph oder ein Leerzeichen lassen sich in der Zone nicht schreiben, also kommt ein gedrucktes Smith-Jones als SMITH<JONES an, nicht zu unterscheiden von einem Nachnamen aus zwei Wörtern. Wenn Sie die Satzzeichen brauchen: Sie stehen auf der gedruckten Seite, nicht in der Zone.

Transliteration: von jeder Schrift zu 26 Buchstaben

Jeder Name muss am Ende in A–Z stehen. Für einen in lateinischen Buchstaben mit diakritischen Zeichen gedruckten Namen heißt das, jeden Buchstaben mit Akzent auf einfache Buchstaben abzubilden. Für einen Namen in griechischer, arabischer, kyrillischer, chinesischer oder einer anderen Schrift heißt das, den ganzen Namen in lateinischen Buchstaben zu schreiben.

Doc 9303 Teil 3, der für alle Formate gemeinsame Teil, enthält empfohlene Transliterationen für beide Fälle. Die Entscheidung liegt aber beim ausstellenden Staat, und nicht alle Staaten entscheiden gleich. Drei Folgen sind für Code wichtig:

  1. Ein gedruckter Name kann mehr als eine Schreibweise in der Zone haben. Ein gedrucktes MÜLLER kann in einer Zone als MUELLER oder als MULLER erscheinen, je nach den Regeln des Ausstellers. Code, der eine einzige feste Abbildung erwartet, erklärt eine korrekte Zone für falsch.
  2. Die Abbildung lässt sich nicht umkehren. MULLER in einer Zone kann auf der Seite von MULLER, MÜLLER oder MÚLLER stammen. Eine Zone sagt Ihnen, welche Buchstaben sie enthält, nicht, wie die Person ihren Namen schreibt.
  3. Der lateinische Name auf der Seite kann von der Zone abweichen. Ein Dokument, das in einer Landesschrift gedruckt ist, druckt oft auch eine lateinische Fassung, und diese gedruckte lateinische Schreibweise stimmt nicht immer Buchstabe für Buchstabe mit der in der Zone überein. Behandeln Sie eine Abweichung als etwas, das man sich ansieht, nicht als Beweis für einen Lesefehler.

Die praktische Regel: Vergleichen Sie einen Zonennamen mit einem gedruckten Namen, nachdem Sie beide auf reine Buchstaben reduziert haben, lassen Sie die Schreibweisen zu, die Ihre Aussteller tatsächlich verwenden, und behalten Sie die gedruckte Schreibweise für alles, was ein Mensch liest.

Kürzung: wenn der Name nicht passt

Ein langer Name wird nicht in die nächste Zeile umbrochen. Er wird gekürzt, bis er ins Feld passt, und der vollständige Name bleibt auf der gedruckten Seite. Der Standard gibt Regeln vor, wie ein Name zu kürzen ist, und ein gekürzter Name reicht normalerweise bis zum allerletzten Zeichen seines Felds.

Daraus ergibt sich das einzige Signal, das ein Parser hat: Ist das letzte Zeichen des Namensfelds ein Buchstabe und nicht <, kann der Name gekürzt sein. Es heißt „kann“, nicht „wurde“: Ein Name, der genau so lang ist wie das Feld, endet ebenfalls auf einem Buchstaben. Ein Feld, das auf Füllzeichen endet, wurde dagegen sicher nicht gekürzt.

Die Breite spielt eine Rolle. Nehmen wir einen erfundenen Inhaber, Konstantin Friedrich Hartmann Schoenebeck. In einem TD3-Feld mit 39 Zeichen:

HARTMANN<SCHOENEBECK<<KONSTANTIN<FRIEDR

Das letzte Zeichen ist R, ein Buchstabe, also kann der Name gekürzt sein, und das ist er auch: FRIEDRICH hat drei Buchstaben verloren. Derselbe Name, auf die TD1-Breite von 30 gekürzt, behält nur HARTMANN<SCHOENEBECK<<KONSTANT. Ein System, das den Zonennamen als Namen des Inhabers speichert, speichert KONSTANT als Vornamen.

Daraus folgen zwei Gewohnheiten. Verwenden Sie nie den Zonennamen als Anzeigenamen, wenn das Feld voll ist; nehmen Sie den gedruckten. Und wenn Sie einen vollständigen gedruckten Namen mit einem Zonennamen abgleichen, ist ein gedruckter Name, der mit dem Zonennamen beginnt, ein Treffer, sofern das Feld voll war.

Ein Aussteller kann einen Namen auch anders kürzen als durch Abschneiden am Ende, etwa indem er einen zweiten Vornamen auf die Initiale verkürzt. Ein Präfixtest meldet einen solchen Namen als Abweichung. Leiten Sie diesen Fall an einen Menschen weiter, statt das Dokument abzulehnen.

Keine Prüfziffer deckt den Namen ab

Die Prüfziffern decken die Dokumentnummer, die beiden Daten, die optionale persönliche Nummer bei TD3 und eine Gesamtprüfziffer über den größten Teil der unteren Zeile ab. Keine davon deckt den Namen ab. Bei TD3 und TD2 steht der Name in Zeile 1, die überhaupt keine Ziffer berührt; bei TD1 steht er in Zeile 3, und die Gesamtprüfziffer endet bei Zeile 2.

Ein Lesefehler im Namen bleibt also stumm. Ein als N gelesenes M oder ein als Buchstabe gelesenes Füllzeichen ergibt immer noch eine Zone, deren Rechnung aufgeht. Die einzige Gegenprüfung für einen Namen ist die gedruckte Seite: Lesen Sie beides und vergleichen Sie, und genau dafür ist die Vergleichsfunktion weiter unten da. So funktionieren die Prüfziffern der MRZ erklärt die Rechnung für die Felder, die abgedeckt sind.

Das Feld in Python zerlegen

Keine Abhängigkeiten. Die Funktion erhält nur das Namensfeld, bereits aus seiner Zeile herausgeschnitten (line1[5:44] bei TD3, line1[5:36] bei TD2, line3 bei TD1).

import re

def words(part):
    return " ".join(word for word in part.split("<") if word)

def read_mrz_name(field):
    """Split an MRZ name field into (primary, secondary, may_be_truncated)."""
    if not re.fullmatch(r"[A-Z<]+", field):
        raise ValueError("a name field holds only A-Z and the filler <")
    may_be_truncated = not field.endswith("<")
    body = field.rstrip("<")
    primary, _separator, secondary = body.partition("<<")
    return words(primary), words(secondary), may_be_truncated

partition trennt nur am ersten << und liefert einen leeren sekundären Teil, wenn es keines gibt – das ist der Fall eines einzigen Namens. Das Muster lehnt Ziffern absichtlich ab: Eine 0 in einem Namensfeld ist ein falsch gelesenes O und sollte bei einem Menschen landen, statt als Name ausgegeben zu werden.

Dieselbe Zerlegung in JavaScript

export function readMrzName(field) {
  if (!/^[A-Z<]+$/.test(field)) throw new Error("a name field holds only A-Z and the filler <");
  const mayBeTruncated = !field.endsWith("<");
  const body = field.replace(/<+$/, "");
  const at = body.indexOf("<<");
  const words = (part) => part.split("<").filter(Boolean).join(" ");
  if (at < 0) return { primary: words(body), secondary: "", mayBeTruncated };
  return { primary: words(body.slice(0, at)), secondary: words(body.slice(at + 2)), mayBeTruncated };
}

Die Zone mit dem gedruckten Namen vergleichen

Der gedruckte Name stammt aus der Sichtzone, dem Teil der Seite, den ein Mensch liest, mit seinen Akzenten und Satzzeichen. Die Funktion unten reduziert ihn auf reine Großbuchstaben und vergleicht ihn mit einem Teil des Zonennamens. Sie gibt eines von drei Wörtern zurück: match, mismatch oder unknown, wenn der gedruckte Name einen Buchstaben enthält, der sich nicht von selbst auf A–Z reduzieren lässt, etwa Ø, Æ oder Ł.

import re
import unicodedata

# Die Schreibweisen, die neben dem bloßen Entfernen des Akzents probiert
# werden. Diese drei sind ein Beispiel; tragen Sie die Ihrer Aussteller ein.
EXPANSIONS = {"Ä": "AE", "Ö": "OE", "Ü": "UE"}

def compare_name(zone_part, printed_part, truncated=False, expansions=EXPANSIONS):
    """'match', 'mismatch' or 'unknown' for one name part: zone against printed."""
    zone = zone_part.replace(" ", "")
    upper = unicodedata.normalize("NFC", printed_part).upper()
    candidates = {upper, "".join(expansions.get(c, c) for c in upper)}
    verdict = "unknown"
    for candidate in candidates:
        letters = "".join(
            c for c in unicodedata.normalize("NFKD", candidate)
            if c.isalpha() and not unicodedata.combining(c)
        )
        if not re.fullmatch(r"[A-Z]*", letters):
            continue  # ein Buchstabe ohne einfache A-Z-Form: einem Menschen überlassen
        if letters == zone or (truncated and letters.startswith(zone)):
            return "match"
        verdict = "mismatch"
    return verdict

NFKD zerlegt einen Buchstaben mit Akzent in den einfachen Buchstaben und seinen Akzent, und der Akzent wird verworfen. Leerzeichen, Bindestriche und Apostrophe werden ebenfalls verworfen, weil nur Buchstaben verglichen werden: Smith-Jones und SMITH JONES werden beide zu SMITHJONES. Bei einem Namen, der nur in einer nichtlateinischen Schrift gedruckt ist, vergleichen Sie die Zone mit der lateinischen Fassung, die die Seite druckt, nicht mit der in der Landesschrift; die Landesschrift würde hier jedes Mal unknown liefern.

Testvektoren

Jeder Name ist erfunden, außer dem ICAO-Muster. In der ersten Tabelle wird jedes Feld mit < auf die TD3-Breite von 39 aufgefüllt (in Python name.ljust(39, "<")), außer der letzten Zeile, die bereits genau 39 Zeichen hat.

Feld vor dem Auffüllen Primär Sekundär Kann gekürzt sein
ERIKSSON<<ANNA<MARIA ERIKSSON ANNA MARIA nein
VAN<DER<BERG<<PIETER VAN DER BERG PIETER nein
SMITH<JONES<<MARY SMITH JONES MARY nein
OKONKWO OKONKWO (leer) nein
NAKAMURA<< NAKAMURA (leer) nein
HARTMANN<SCHOENEBECK<<KONSTANTIN<FRIEDR HARTMANN SCHOENEBECK KONSTANTIN FRIEDR ja

Und für compare_name:

Zonenteil Gedruckter Teil Gekürzt Erwartet
ANNA MARIA Anna Maria nein match
SMITH JONES Smith-Jones nein match
MULLER Müller nein match
MUELLER Müller nein match (über die Erweiterung)
MILLER Müller nein mismatch
KONSTANTIN FRIEDR Konstantin Friedrich ja match
KONSTANTIN FRIEDR Konstantin Friedrich nein mismatch
SORENSEN Sørensen nein unknown

Die letzte Zeile ist Absicht. Ob aus Ø ein O oder ein OE wurde, entscheidet der Aussteller, und eine Funktion, die rät, liegt für irgendjemanden falsch; tragen Sie stattdessen die Form, die Ihre Aussteller verwenden, in die Erweiterungen ein.

Was unser MRZ-Parser macht

Der MRZ-Parser auf dieser Website liest einen Namen auf dieselbe Weise: Er entfernt die Füllzeichen am Ende, trennt am ersten << und wandelt einzelne Füllzeichen in Leerzeichen um; ein Feld ohne << wird als bloßer Nachname gelesen. Reicht der Name bis zum letzten Zeichen seines Felds, zeigt er eine Warnung, dass der Name zum Einpassen gekürzt sein könnte, mit den Positionen, die er belegt. Er läuft im Browser, Sie können also eine synthetische Zone einfügen und seine Lesart mit Ihrem eigenen Code vergleichen.

Wo das in eine Pipeline passt

Ein gehosteter Erkennungsdienst liest sowohl die Zone als auch die gedruckte Seite, die Frage ist also, welchen Namen er Ihnen übergibt. In der Antwort von doc.cheap enthalten holder.surname, holder.given_names und holder.full_name die zusammengeführte lateinische Lesart. Ein in einer Landesschrift gedruckter Name ist ein eigener Eintrag in fields: Jede Lesart hat ihren eigenen Eintrag, die lateinische Standardlesart hat eine id, die auf @0 endet, und die Lesart in der Landesschrift nennt ihre language, zum Beispiel Greek. Die Zone selbst steht in mrz.lines, genau so, wie sie gelesen wurde, damit Sie das Namensfeld herausschneiden und die beiden Funktionen oben darauf anwenden können. Der Leitfaden zu nichtlateinischen Schriften zeigt, wie man die Lesarten gruppiert und beide Schreibweisen speichert. Wenn Sie noch einen Dienst auswählen, stellt der Vergleich von Reisepass-OCR-APIs die veröffentlichten Preise nebeneinander.

Wenn Sie einen Namen finden, den der Code oben falsch liest, schreiben Sie an admin@doc.cheap.

Eine Frage an Sie: Welche Transliteration hat Sie beim Vergleich einer Zone mit dem gedruckten Namen am meisten überrascht? Schreiben Sie es uns unten in die Kommentare.