İsim, makine tarafından okunabilir bölgenin en az öngörülebilir alanıdır. Tarihler altı rakamdır, belge numarası kontrol haneli dokuz karakterdir ve kodlar sabit bir listeden gelir. İsim ise bir kişiye ne deniyorsa odur: sabit sayıda büyük harfe sıkıştırılmış, boşluksuz, aksansız, noktalamasız ve bir karakterin yanlış okunduğunu size söyleyecek bir kontrol hanesi olmadan.

Bu yazı, bölgenin bir ismi nasıl yazdığını, onu yeniden soyadı ve adlara nasıl ayıracağınızı, harf çevirisinin isme ne yaptığını, ismin kısaltılmış olabileceğini nasıl anlayacağınızı ve onu sayfada basılı isimle nasıl karşılaştıracağınızı anlatıyor. Aşağıdaki her isim uydurmadır ya da ICAO'nun kendi örneği olan Anna Maria Eriksson'dur; hiçbir gerçek kişi yer almıyor.

Burası doc.cheap'in blogu; doc.cheap bir pasaport ve kimlik OCR API'si. Aşağıdakilerin hiçbiri ona ihtiyaç duymuyor; kodun hiçbir bağımlılığı yok.

İsim nerede durur

Makine tarafından okunabilir seyahat belgelerinin standardı olan ICAO Doc 9303, üç düzeninin her birinde isme sabit genişlikte bir alan ayırır. Konumlar burada standardın saydığı gibi 1'den başlar:

Biçim İsmin yeri Genişlik
TD3 (pasaportlar), 2 × 44 1. satır, 6–44. konumlar 39 karakter
TD2, 2 × 36 1. satır, 6–36. konumlar 31 karakter
TD1 (kimlik kartları), 3 × 30 3. satır, satırın tamamı 30 karakter

TD3 veya TD2'nin 1. satırındaki ilk beş karakter belge kodu ve düzenleyen devlettir; isim bunlardan sonraki her şeyi kaplar. TD1 isme ayrı bir satır verir ve yine de üçü arasında en dar olanıdır. MRZ biçimleri sayfası her düzeni alan alan anlatır.

TD3 örneğinde 1. satır P<UTOERIKSSON<<ANNA<MARIA<<<<<<<<<<<<<<<<<<< şeklindedir. P<UTO kısmını atın; isim alanı ERIKSSON<<ANNA<MARIA olur ve ardından 39 karaktere kadar dolgu karakterleri gelir.

Dilbilgisi: <<, < ve dolgu

Bölgenin alfabesi A–Z, 0–9 ve dolgu karakteri <'dir. Bir isim bunun üzerine üç kural kullanır:

  • Birincil tanımlayıcı önce gelir. Bu genellikle soyadıdır.
  • İki dolgu karakteri, <<, birincil tanımlayıcıyı bitirir. Ardından gelen ikincil tanımlayıcıdır, genellikle adlar.
  • Tek bir dolgu karakteri, <, her iki bölümün içindeki sözcükleri ayırır. ANNA<MARIA iki addır; VAN<DER<BERG üç sözcüklü bir soyadıdır.

İsimden sonra alanda kalan her yer < ile doldurulur. Dolayısıyla bir ismi geri okumak üç adımdır: sondaki dolgu karakterlerini atın, ilk <<'de bölün ve kalan tekli < karakterlerini boşluğa çevirin.

İki biçim tuhaf görünür ama hata değildir. Tek ismi olan bir kişinin yalnızca birincil tanımlayıcısı vardır, bu yüzden alan önce isim, sonra dolgu karakterleridir ve öncesinde << yoktur: OKONKWO<<<<…. Ayırıcıyla yazılmış ama ardında hiçbir şey olmayan bir alan, NAKAMURA<<<<…, sondaki dolgu karakterleri atıldığında tamamen aynı okunur. Her iki durumda da ikincil tanımlayıcı boştur, eksik değildir.

Korunamayan şey noktalama ve boşluğun kendisidir. Kısa çizgi, kesme işareti veya boşluk bölgeye yazılamaz; bu yüzden basılı bir Smith-Jones, iki sözcüklü bir soyadından ayırt edilemeyen SMITH<JONES olarak gelir. Noktalamaya ihtiyacınız varsa, o bölgede değil basılı sayfadadır.

Harf çevirisi: her yazı sisteminden 26 harfe

Her isim sonunda A–Z içine girmek zorundadır. Aksan işaretli Latin harflerle basılmış bir isim için bu, her aksanlı harfi düz harflere eşlemek demektir. Yunanca, Arapça, Kiril, Han veya başka herhangi bir yazıdaki bir isim için ise ismin tamamını Latin harfleriyle yazmak demektir.

Doc 9303'ün tüm biçimlerde ortak olan Bölüm 3'ü her iki durum için önerilen harf çevirilerini içerir. Ancak karar düzenleyen devletindir ve devletlerin hepsi aynı seçimi yapmaz. Kod açısından üç sonuç önemlidir:

  1. Basılı bir ismin bölgede birden fazla yazımı olabilir. Basılı bir MÜLLER, düzenleyenin kurallarına göre bölgede MUELLER veya MULLER olarak görünebilir. Tek bir sabit eşleme bekleyen kod doğru bir bölgeyi yanlış ilan eder.
  2. Eşleme geri çevrilemez. Bölgedeki MULLER, sayfadaki MULLER, MÜLLER veya MÚLLER'den gelmiş olabilir. Bölge size içerdiği harfleri söyler, kişinin yazdığı ismi değil.
  3. Sayfadaki Latin isim bölgeden farklı olabilir. Ulusal bir yazıyla basılmış belge çoğu zaman bir Latin sürümünü de basar ve bu basılı Latin yazımı her zaman bölgedekiyle harfi harfine aynı değildir. Bir farkı yanlış okumanın kanıtı olarak değil, incelenecek bir şey olarak ele alın.

Pratik kural: bölgedeki bir ismi basılı bir isimle, ikisini de yalın harflere indirdikten sonra karşılaştırın, düzenleyenlerinizin gerçekten kullandığı yazımlara pay bırakın ve bir insanın okuyacağı her şey için basılı yazımı saklayın.

Kısaltma: isim sığmadığında

Uzun bir isim başka bir satıra kaymaz. Alana sığacak şekilde kısaltılır ve tam isim basılı sayfada kalır. Standart bir ismin nasıl kısaltılacağına dair kurallar verir ve kısaltılmış bir isim normalde alanının en son karakterine kadar uzanır.

Bu da ayrıştırıcıya sahip olduğu tek işareti verir: isim alanının son karakteri < değil de bir harfse, isim kısaltılmış olabilir. "Kısaltılmıştır" değil, "olabilir": alanla tam olarak aynı uzunluktaki bir isim de bir harfle biter. Ama dolgu karakterleriyle biten bir alan kesinlikle kesilmemiştir.

Genişlik önemlidir. Uydurma bir hamil alalım: Konstantin Friedrich Hartmann Schoenebeck. 39 karakterlik bir TD3 alanında:

HARTMANN<SCHOENEBECK<<KONSTANTIN<FRIEDR

Son karakter R, yani bir harf; dolayısıyla isim kesilmiş olabilir ve gerçekten de kesilmiştir: FRIEDRICH üç harf kaybetmiştir. Aynı isim 30 karakterlik TD1 genişliğinde kesildiğinde yalnızca HARTMANN<SCHOENEBECK<<KONSTANT kalır. Bölgedeki ismi hamilin ismi olarak kaydeden bir sistem, ilk ad olarak KONSTANT kaydeder.

Buradan iki alışkanlık çıkar. Alan doluyken bölgedeki ismi asla görüntülenen isim olarak kullanmayın; basılı olanı kullanın. Tam bir basılı ismi bölgedeki bir isimle eşleştirirken de, alan doluysa bölgedeki isimle başlayan bir basılı isim eşleşme sayılır.

Düzenleyen bir ismi sonunu kesmekten başka yollarla da kısaltabilir; örneğin ikinci bir adı baş harfine indirerek. Önek testi böyle bir ismi uyuşmazlık olarak bildirir. Bu durumu belgeyi reddetmek yerine bir insana yönlendirin.

İsmi hiçbir kontrol hanesi kapsamaz

Kontrol haneleri belge numarasını, iki tarihi, TD3'teki isteğe bağlı kişisel numarayı ve alt satırın büyük kısmı üzerinde bileşik bir haneyi kapsar. Hiçbiri ismi kapsamaz. TD3 ve TD2'de isim, hiçbir hanenin dokunmadığı 1. satırdadır; TD1'de 3. satırdadır ve bileşik hane 2. satırda durur.

Bu yüzden bir isimdeki yanlış okuma sessizdir. N olarak okunan bir M ya da harf olarak okunan bir dolgu karakteri, aritmetiği yine tutan bir bölge verir. Bir isim için tek çapraz kontrol basılı sayfadır: ikisini de okuyup karşılaştırın; aşağıdaki karşılaştırma fonksiyonu bunun içindir. MRZ kontrol haneleri nasıl çalışır yazısı, kapsanan alanların aritmetiğini anlatır.

Alanı Python'da bölmek

Bağımlılık yok. Fonksiyon yalnızca satırından önceden kesilip alınmış isim alanını alır (TD3'te line1[5:44], TD2'de line1[5:36], TD1'de line3).

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 yalnızca ilk <<'de böler ve ayırıcı yoksa boş bir ikincil bölüm döndürür; bu tek isim durumudur. Desen rakamları bilerek reddeder: isim alanındaki bir 0 yanlış okunmuş bir O'dur ve isim olarak basılmak yerine bir insana ulaşmalıdır.

Aynı bölme JavaScript'te

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 };
}

Bölgeyi basılı isimle karşılaştırmak

Basılı isim, sayfanın bir insanın okuduğu kısmı olan görsel bölgeden, aksanları ve noktalamasıyla birlikte gelir. Aşağıdaki fonksiyon onu yalın büyük harflere indirir ve bölgedeki ismin bir bölümüyle karşılaştırır. Üç sözcükten birini döndürür: match, mismatch ya da basılı isim kendi başına A–Z'ye inmeyen bir harf içerdiğinde, örneğin Ø, Æ veya Ł, unknown.

import re
import unicodedata

# The spellings to try besides plain removal of the accent. These three are
# an example; put in the ones your issuers use.
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  # a letter with no plain A-Z form: leave it to a person
        if letters == zone or (truncated and letters.startswith(zone)):
            return "match"
        verdict = "mismatch"
    return verdict

NFKD aksanlı bir harfi düz harf ve aksanı olarak ayırır, aksan da atılır. Boşluklar, kısa çizgiler ve kesme işaretleri de atılır, çünkü karşılaştırma yalnızca harfler üzerindedir: Smith-Jones ve SMITH JONES ikisi de SMITHJONES olur. Yalnızca Latin olmayan bir yazıyla basılmış bir isim için bölgeyi, ulusal yazıdaki sürümle değil, sayfanın bastığı Latin sürümle karşılaştırın; ulusal yazı burada her seferinde unknown döndürür.

Test vektörleri

ICAO örneği dışındaki her isim uydurmadır. İlk tabloda her alan < ile 39'luk TD3 genişliğine kadar doldurulur (Python'da name.ljust(39, "<")); yalnızca son satır zaten tam 39 karakterdir.

Dolgudan önceki alan Birincil İkincil Kısaltılmış olabilir
ERIKSSON<<ANNA<MARIA ERIKSSON ANNA MARIA hayır
VAN<DER<BERG<<PIETER VAN DER BERG PIETER hayır
SMITH<JONES<<MARY SMITH JONES MARY hayır
OKONKWO OKONKWO (boş) hayır
NAKAMURA<< NAKAMURA (boş) hayır
HARTMANN<SCHOENEBECK<<KONSTANTIN<FRIEDR HARTMANN SCHOENEBECK KONSTANTIN FRIEDR evet

Ve compare_name için:

Bölge bölümü Basılı bölüm Kısaltılmış Beklenen
ANNA MARIA Anna Maria hayır match
SMITH JONES Smith-Jones hayır match
MULLER Müller hayır match
MUELLER Müller hayır match (genişletme yoluyla)
MILLER Müller hayır mismatch
KONSTANTIN FRIEDR Konstantin Friedrich evet match
KONSTANTIN FRIEDR Konstantin Friedrich hayır mismatch
SORENSEN Sørensen hayır unknown

Son satır bilinçlidir. Ø'nun O mu yoksa OE mi olduğu düzenleyenin seçimidir ve tahmin yürüten bir fonksiyon birileri için yanılır; bunun yerine düzenleyenlerinizin kullandığı biçimi genişletmelere ekleyin.

MRZ ayrıştırıcımız ne yapar

Bu sitedeki MRZ ayrıştırıcı bir ismi aynı şekilde okur: sondaki dolgu karakterlerini atar, ilk <<'de böler ve tekli dolgu karakterlerini boşluğa çevirir; << içermeyen bir alan yalnızca soyadı olarak okunur. İsim alanının son karakterine kadar uzandığında, ismin sığması için kısaltılmış olabileceğine dair bir uyarı gösterir ve kapladığı konumları belirtir. Tarayıcıda çalışır; böylece sentetik bir bölge yapıştırıp okumasını kendi kodunuzla karşılaştırabilirsiniz.

Bunun bir işlem hattındaki yeri

Barındırılan bir tanıma hizmeti hem bölgeyi hem de basılı sayfayı okur; dolayısıyla soru, size hangi ismi verdiğidir. doc.cheap yanıtında holder.surname, holder.given_names ve holder.full_name birleştirilmiş Latin okumayı taşır. Ulusal bir yazıyla basılmış isim, fields içinde ayrı bir girdidir: her okumanın kendi girdisi vardır, varsayılan Latin okumanın id değeri @0 ile biter ve ulusal yazıdaki okuma kendi language değerini belirtir, örneğin Greek. Bölgenin kendisi tam okunduğu haliyle mrz.lines içindedir; böylece isim alanını kesip alarak yukarıdaki iki fonksiyonu üzerinde çalıştırabilirsiniz. Latin olmayan yazılar rehberi okumaların nasıl gruplanacağını ve iki yazımın birlikte nasıl saklanacağını gösterir. Hâlâ bir hizmet seçiyorsanız, pasaport OCR API karşılaştırması yayımlanmış fiyatları yan yana koyar.

Yukarıdaki kodun yanlış okuduğu bir isim bulursanız admin@doc.cheap adresine yazın.

Size bir soru: bir bölgeyi basılı isimle karşılaştırdığınızda sizi en çok hangi harf çevirisi şaşırttı? Aşağıdaki yorumlarda bize anlatın.