OCR metin katmanları, barkod sınırları ve yüz maskeleme kutuları, bitmap pixel'leri renderer'ın aslında rasterize ettiği kutu yerine — MediaBox'a kırpılmış CropBox (ISO 32000-1 §14.11.2) yerine — MediaBox üzerinden geri eşlendiğinde kırpılmış PDF sayfalarında kayar. HotPDF bunu ApplyLoadedOCRTextLayer için v2.770.153'te, DecodeLoadedPageBarcodes ile DetectLoadedRedactionFindings için v2.770.154'te düzeltti
Genelde ulaşan hata raporu şöyle görünür. Taranmış bir sözleşme arşivi OCR'dan geçer, çıktı aranabilirdir ve bir madde numarasının arama isabeti, basılı numaranın yarım inç aşağısında ve solunda vurgulanır. Topluluktaki dosyaların çoğu sağlamdır. Bozuk olanların hepsi, platen kenar boşluğunu kırpmak için /CropBox yazan tek bir tarama istasyonundan gelmiştir. O tek ayrıntı, OCR motorunun gördüğü resmi metin katmanının yerleştirildiği çerçeveden ayırır ve aynı uyumsuzluk barkod sınırlarını, daha ciddisi yüz maskeleme kutularını oynatır
OCR metin katmanı taranmış kelimelerden neden uzaklaşır?
Metin katmanı kayar, çünkü hattın iki yarısı bitmap'in hangi dikdörtgeni kapsadığı konusunda anlaşamıyordu. v2.766.64'te HotPDF, rendering'i, SVG export'unu, viewer'ı ve yazdırmayı CropBox'a uyduracak biçimde değiştirdi: bir sayfa, CropBox'ının MediaBox'a kırpılmış hâliyle — ISO 32000-1 §14.11.2'nin öngördüğü gibi — görüntülenir ve o görünür kutuyu döndürmek için GetLoadedPageVisibleBox eklendi. Tanıma özellikleri ise device-to-page dönüşümünü kurmaya GetLoadedPageBox(PageIndex, pbMediaBox, ...) ile devam ediyordu. Raster artık görünür kutuyu kapsıyor, dönüşüm hâlâ MediaBox varsayıyor ve tanınan her konum ikisinin arasındaki boşluk kadar kaymış dönüyordu
Etkilenen pencere bu yüzden kesindir. ApplyLoadedOCRTextLayer, metni v2.766.64'ten v2.770.152'ye dek yanlış yere koydu. Bütün sayfalık DecodeLoadedPageBarcodes ile DetectLoadedRedactionFindings içindeki yüz detection'ı bir derleme daha uzun, v2.770.153'e dek yanlış kaldı. v2.766.64 öncesinde renderer bütün MediaBox'ı çiziyordu; eşleme ile raster hemfikirdi — karşılığında görüntüleyicilerin hiç göstermediği içeriği tanıma pahasına. Düzeltmeler her özellik için üç şeyi birlikte değiştirdi: dönüşümü, pixel bütçesi tahminini ve istek kaydında özel bir motora verilen page box'ı
Birkaç olgu hiç etkilenmedi:
/CropBoxı olmayan ya da CropBox'ı MediaBox'a eşit olan sayfalar, düzeltme öncesi ve sonrasında birebir aynı eşlenirHasRegionsetliDecodeLoadedPageBarcodes, tam olarak geçirdiğiniz bölgeyi render eder ve aynı bölge üzerinden eşler; dolayısıyla açık bölge decode'u baştan sona doğrudu. Bölgenin sayfa içinde kaldığını denetleyen kontrol hâlâ MediaBox'ı kullanır- Desen tabanlı maskeleme bulguları (e-postalar, kart numaraları vb.) user space'te metin extraction'ından gelir, raster'dan değil; dolayısıyla yalnızca yüz detection bulguları oynadı
Üç koordinat çerçevesi ve her birini hangi HotPDF API'leri kullanır?
Tanımayla temas eden HotPDF kodu üç çerçeveyle uğraşır ve eşleme hatalarının çoğu ikisini karıştırmaktan doğar
- Bitmap pixel'leri: sol-üst köken, Y aşağı büyür, birimler istek DPI'ındaki pixel'lerdir.
THPDFOCRWord.Left,Top,RightveBottombu çerçevededir; isteğe bağlı taban noktaları, özel birIHPDFBarcodeDecoder'ın döndürdüğü sonuçlar ve özel birIHPDFFaceDetector'dan gelen kutular da öyledir - Yüklenen bir sayfanın PDF user space'i: sol-alt köken, Y yukarı büyür, birimler point,
Bottom < Top.GetLoadedPageBoxileGetLoadedPageVisibleBoxLeft, Bottom, Right, Top'ı bu çerçevede döndürür;THPDFOCRRequest'inPageLeft,PageBottom,PageRightvePageTopalanları,THPDFDecodedBarcodeiçindeki sınırlar veTHPDFRedactionFindingiçindeki dikdörtgenler de öyledir - HotPDF page-drawing koordinatları: yeni sayfalar kurmak için kullandığınız API (metin çıktısı, şekiller, barkodlar, linkler, form alanları) sol-üst kökenle ve Y aşağı büyüyerek çalışır. O çerçeve belge üretimine aittir ve yukarıdaki yüklenen-belge API'leriyle hiçbir ilgisi yoktur; dolayısıyla yüklenen-sayfa user-space dikdörtgenini değiştirmeden ona asla beslemeyin
OCR kelime kaydı kasıtlı olarak pixel tabanlıdır: bir motor image'de gördüğünü bildirir ve dönüşümü ApplyLoadedOCRTextLayer sahiplenir. O bölünme yalnızca dönüşüm doğru kutuyu kullandığında çalışır; v2.770.153'ün geri getirdiği de buydu
OCR, barkod ve yüzlerin ardındaki device-to-page dönüşümü
HotPDF, bitmap pixel'lerini beş girdiden kurulan tek bir affine matrisle sayfaya eşler: rotasyon, DPI / 72 ölçeği, bitmap yüksekliği ve render edilen kutunun Left, Bottom, Right, Top'ı. OCR, barkod decode'u ve yüz detection bunun için tek bir yordamı paylaşır; tek bir yanlış kutu girdisinin üçünü de aynı biçimde bozması bu yüzden. Döndürülmemiş bir sayfa için page-to-device matrisi [A B C D E F] şudur:
A = ScaleveD = -Scale,Scale = DPI / 72iken; negatifD, user space'i (Y yukarı) bitmap space'ine (Y aşağı) çevirirB = C = 0, çünkü döndürülmemiş bir sayfanın eksenler arasında shear'ı ya da takası yokturE = -Left * Scale, kutunun sol kenarını 0 numaralı pixel sütununa taşırF = BitmapHeight + Bottom * Scale, kutunun alt kenarını bitmap'in alt kenarı olan y = BitmapHeight'a eşler; böylece üst kenar 0. satıra düşer
Pixeller o matrisin tersiyle sayfaya geri gider. Request.PageRotation, sayfanın /Rotate'ini 0, 90, 180 ya da 270'e normalize edilmiş taşır (90'ın katı olmayan her değer 0 sayılır) ve renderer, ISO 32000-1 §7.7.3.3'ün istediği gibi sayfayı saat yönünde döndürür. Rotasyon altında eksenler takas olur ve kutu kenarlarından başka bir çift bitmap kökenine sabitlenir. Ters formüller olarak yazılmış hâliyle, S = DPI / 72, pixel cinsinden x ile y ve bitmap yüksekliği H iken:
| /Rotate | Sayfa X | Sayfa Y | Eşlemenin dayandığı kutu kenarları |
|---|---|---|---|
| 0 | Left + x / S | Bottom + (H - y) / S | Left, Bottom |
| 90 | Left + y / S | Bottom + x / S | Left, Bottom |
| 180 | Right - x / S | Bottom + y / S | Right, Bottom |
| 270 | Right - y / S | Top - x / S | Right, Top |
Son sütun, hatanın üretimde neden rastgele göründüğünü açıklar. Yalnızca sayfanın üstünü kırpılan bir CropBox, Left ile Bottom'a dokunmaz; dik sayfalar kusursuz çıkar ve yalnızca /Rotate 270 taşıyan sayfalar kayardı. Rotasyon ayrıca bitmap boyutlarını takas eder: 90 ile 270'te bitmap (Top - Bottom) * S pixel genişliğinde ve (Right - Left) * S pixel yüksektir
MediaBox [0 0 612 792] ile CropBox [36 36 576 756]'da ne yanlış gider?
Her kenarda yarım inçlik bir kırpma iken, MediaBox kullanıldığında döndürülmemiş bir sayfanın metin katmanı tam olarak taranmış kelimelerin 36 point soluna ve 36 point altına düşer. Her kenardan 36 point (0.5 inç) kırpan CropBox'ı olan bir US Letter sayfası alın. Görünür kutu 540'a 720 point'tir; dolayısıyla varsayılan 300 DPI OCR çözünürlüğünde ölçek 300 / 72 ≈ 4,1667'dir ve bitmap 2250'ye 3000 pixeldir
Motorun, taban çizgisi olmadan Left 450, Top 600, Right 900, Bottom 660 pixel kutulu bir kelime bildirdiğini varsayın. HotPDF sonra taban çizgisini alt kenarın %20 yukarısına, 648. pixel satırına koyar ve başlangıç noktasını (450, 648) eşler:
- Görünür kutu üzerinden: x = 36 + 450 / 4,1667 = 144,0 ve y = 36 + (3000 - 648) / 4,1667 = 600,48 — kelimenin basılı olduğu yer
- MediaBox üzerinden: x = 0 + 108,0 = 108,0 ve y = 0 + 564,48 = 564,48 — (-36, -36) pointlik düzgün bir kayma
Aynı sayfayı döndürün, hatanın yönü değişir; çünkü işin içinde farklı kenarlar vardır. /Rotate 180'de X terimi Right'ı kullanır ve 576 yerine 612, katmanı 36 point sağa iterken Bottom onu hâlâ 36 point aşağı çeker. /Rotate 270'te Right ile Top ikisi de fazla büyüktür; katman 36 point sağa ve 36 point yukarı gider. Karışık yönelimli bir belge kaymayı üç yönde gösterebilir — bu hata için güvenilir bir parmak izi. Ölçeği kutudan türeyen elle yazılmış kod, örneğin Bitmap.Width / (Right - Left), ofsetin üstüne her koordinatı 612 / 540, kabaca %13, kadar da gerer
Hangi PDF belgeleriniz etkilenir?
Bir PDF belgesi, en az bir sayfası MediaBox'ından farklı bir görünür kutu taşıdığında maruziyet altındadır ve HotPDF bunu size birkaç satırda söyleyebilir. Her sayfa için pbMediaBoxlu GetLoadedPageBox'ı GetLoadedPageVisibleBox ile karşılaştırın ve kayma yönünü yukarıdaki tablodan öngörebilmeniz için yanına GetLoadedPageRotation'ı da yazdırın. THPDFPageBoundary ayrıca pbCropBox, pbBleedBox, pbTrimBox ve pbArtBox sunar; ama GetLoadedPageBox(pbCropBox), crop kutusu yokken MediaBox'a döner ve kırpma yapmaz — dolayısıyla karşılaştırılacak doğru şey görünür kutudur
uses
System.SysUtils, HPDFDoc;
procedure ReportCroppedPages(const FileName: string);
var
Pdf: THotPDF;
I: Integer;
ML, MB, MR, MT, VL, VB, VR, VT, Tmp: Single;
begin
Pdf := THotPDF.Create(nil);
try
if Pdf.LoadFromFile(FileName) < 1 then
raise Exception.Create('Cannot load ' + FileName);
for I := 0 to Pdf.LoadedPageCount - 1 do
begin
if not Pdf.GetLoadedPageBox(I, pbMediaBox, ML, MB, MR, MT) then
Continue;
// saklanan dizi köşelerini herhangi bir sırada listeliyor olabilir
if MR < ML then begin Tmp := ML; ML := MR; MR := Tmp; end;
if MT < MB then begin Tmp := MB; MB := MT; MT := Tmp; end;
// hâlihazırda normalize edilmiş ve MediaBox'a kırpılmış
if not Pdf.GetLoadedPageVisibleBox(I, VL, VB, VR, VT) then
Continue;
if (Abs(VL - ML) > 0.01) or (Abs(VB - MB) > 0.01) or
(Abs(VR - MR) > 0.01) or (Abs(VT - MT) > 0.01) then
Writeln(Format('Page %d MediaBox [%g %g %g %g] visible [%g %g %g %g] /Rotate %d',
[I + 1, ML, MB, MR, MT, VL, VB, VR, VT,
Pdf.GetLoadedPageRotation(I)]));
end;
finally
Pdf.Free;
end;
end;
GetLoadedPageVisibleBox'in iki ayrıntısı, bunun gibi script'ler için önemlidir. Fonksiyon, başarısız olduğunda out parametrelerine dokunmaz; dolayısıyla çağrıdan önce varsayılan bir sayfa boyutu önlemek güvenli bir örüntüdür. Ve bozuk bir CropBox MediaBox'la hiç kesişmediğinde fonksiyon, boş bir dikdörtgen yerine MediaBox'ı döndürür. Rapor sayfa listeliyorsa ve dağıtımdaki derlemeniz OCR için v2.770.153'ten, barkod ve yüzler için v2.770.154'ten eskiyse, yükselttikten sonra o sayfalarda tanımayı yeniden koşturun. Etkilenen bir derlemenin işlediği OCR katmanı kaydedilen dosyada kalır ve varsayılan SkipPagesWithText option'ı, kapatmadığınız ya da eski katmanı önce kaldırmadığınız sürece ikinci geçişte o sayfaları atlar
Özel bir IHPDFOCREngine pixel'leri PDF space'ine nasıl geri eşlemeli?
Özel bir IHPDFOCREngine, kelime kutularını bitmap pixel'leri olarak döndürmeli ve eşlemeyi HotPDF'e bırakmalı; user space'e yalnızca kendi kararları için çevirmeli ve o zaman da isteğin kutusunu kullanmalı, asla MediaBox'ı değil. v2.770.153'ten beri isteğin PageLeft, PageBottom, PageRight ve PageTop'u render edilen görünür kutuyu tanımlar; dolayısıyla Request.Bitmap ile tam olarak uyuşurlar. Aşağıdaki yardımcı, kütüphanenin dönüşümünün tersidir — dik sayfalar için gerçek bitmap yüksekliğini kullanması dahil — dolayısıyla HotPDF'le pixel pixel hemfikirdir
uses
System.SysUtils, System.Math, Vcl.Graphics, HPDFDoc;
// Bitmap pixeli (sol-üst köken, Y aşağı) PDF user space'ine
// (sol-alt köken, Y yukarı), bitmap'in render edildiği kutu üzerinden
procedure HotPixelToPage(Rotation, DPI, BitmapHeight: Integer;
Left, Bottom, Right, Top: Single; X, Y: Double;
out PageX, PageY: Double);
var
S: Double;
begin
S := DPI / 72.0;
case Rotation of
90: begin PageX := Left + Y / S; PageY := Bottom + X / S; end;
180: begin PageX := Right - X / S; PageY := Bottom + Y / S; end;
270: begin PageX := Right - Y / S; PageY := Top - X / S; end;
else
PageX := Left + X / S;
PageY := Bottom + (BitmapHeight - Y) / S;
end;
end;
Bir motorun user space'e gerçekten ihtiyaç duymasının gerçekçi bir nedeni bölge kuralıdır: antetinin asla aranabilir olmasını istemediğiniz faturalar ya da tanıyıcıyı şaşırtan bir damga alanı. Aşağıdaki motor — ömrünü reference counting'in yönetmesi için TInterfacedObject ile yazılmış — kelimeleri merkezlerinin sayfaya düştüğü yere göre filtreler ve kalanları pixel koordinatlarında dokunmadan döndürür. RunRecognizer, kendi tanıyıcı çağrınızın yerini tutar
type
TZoneFilterOCREngine = class(TInterfacedObject, IHPDFOCREngine)
private
FSkipLeft, FSkipBottom, FSkipRight, FSkipTop: Single; // user space
function RunRecognizer(Bitmap: TBitmap; MaxWords: Integer;
out Words: THPDFOCRWords): boolean; // kendi tanıyıcınız, pixel kutuları
public
constructor Create(SkipLeft, SkipBottom, SkipRight, SkipTop: Single);
function GetName: AnsiString;
function Recognize(const Request: THPDFOCRRequest;
out Words: THPDFOCRWords; out Diagnostic: AnsiString): boolean;
end;
function TZoneFilterOCREngine.Recognize(const Request: THPDFOCRRequest;
out Words: THPDFOCRWords; out Diagnostic: AnsiString): boolean;
var
Raw: THPDFOCRWords;
I, Count: Integer;
CX, CY: Double;
begin
Diagnostic := '';
SetLength(Words, 0);
if not RunRecognizer(Request.Bitmap, Request.MaxWords, Raw) then
begin
Diagnostic := 'recognizer failed';
Exit(False);
end;
SetLength(Words, Length(Raw));
Count := 0;
for I := 0 to High(Raw) do
begin
HotPixelToPage(Request.PageRotation, Request.DPI,
Request.Bitmap.Height, Request.PageLeft, Request.PageBottom,
Request.PageRight, Request.PageTop,
(Raw[I].Left + Raw[I].Right) / 2, (Raw[I].Top + Raw[I].Bottom) / 2,
CX, CY);
if (CX >= FSkipLeft) and (CX <= FSkipRight) and
(CY >= FSkipBottom) and (CY <= FSkipTop) then
Continue;
Words[Count] := Raw[I]; // hâlâ pixel: HotPDF kendisi eşler
Inc(Count);
end;
SetLength(Words, Count);
Result := True;
end;
Motoru, diğer her motor gibi ApplyLoadedOCRTextLayer(PageIndices, Engine, Options, Info)'a verin. Kütüphane, ona güvenmeden önce geri döneni doğrular: kutusu bitmap'ten çıkan, Right <= Left ya da Bottom <= Top olan ya da Confidence'i 0-1 aralığının dışında ya da MinimumConfidence'in altında olan bir kelime düşürülür ve Info.DroppedWordCount'te sayılır. MaxWordsPerPage'den çok kelime döndürmek ya da yürüyen toplamı MaxTotalWords'ün ötesine itmek, bütün çağrıyı bir bütçe hatasıyla düşürür; o yüzden motor içinde Request.MaxWords'e saygı gösterin. Kelime kutularını döndürmeden önce user space'e çevirmeyin; HotPDF point değerlerini pixel sayar ve katman bitmap kökenine doğru çökerdi
Kendi detector çıktınızı eşlemek
Aynı yardımcı, RenderLoadedPageToBitmap üzerine kurulu yerli bir hataya da hizmet eder; o da tanıma özellikleri gibi görünür kutuyu render eder ve /Rotate'i uygular. Kutuyu GetLoadedPageVisibleBox ile okuyun, rotasyonu HotPDF'in yaptığı gibi normalize edin ve her pixel kutusunun birbirine ters iki köşesini eşleyin. Y ekseni döner ve 90 ile 270 derecede eksenler takas olur; dolayısıyla eşlenen köşeler sabit bir sırada çıkmaz. Eşlenen noktaların en küçüğü ile en büyüğünü alın — HotPDF barkod sınırlarını da böyle kurar
const
DPI = 200;
var
Pdf: THotPDF;
Bmp: TBitmap;
VL, VB, VR, VT: Single;
Rotation: Integer;
PxL, PxT, PxR, PxB, X1, Y1, X2, Y2: Double;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.LoadFromFile('scanned-ids.pdf');
if not Pdf.GetLoadedPageVisibleBox(0, VL, VB, VR, VT) then Exit;
Rotation := Pdf.GetLoadedPageRotation(0) mod 360;
if Rotation < 0 then Inc(Rotation, 360);
if (Rotation <> 90) and (Rotation <> 180) and (Rotation <> 270) then
Rotation := 0;
Bmp := Pdf.RenderLoadedPageToBitmap(0, DPI);
if Bmp = nil then Exit;
try
MyDetector(Bmp, PxL, PxT, PxR, PxB); // kendi kodunuz, pixel kutusu
HotPixelToPage(Rotation, DPI, Bmp.Height, VL, VB, VR, VT,
PxL, PxT, X1, Y1);
HotPixelToPage(Rotation, DPI, Bmp.Height, VL, VB, VR, VT,
PxR, PxB, X2, Y2);
Writeln(Format('User-space box [%.2f %.2f %.2f %.2f]',
[Min(X1, X2), Min(Y1, Y2), Max(X1, X2), Max(Y1, Y2)]));
finally
Bmp.Free;
end;
finally
Pdf.Free;
end;
end;
Rotasyon davranışı page box'ları bozmadan sayfa rotasyonunu düzleştirmekte, aynı dönüşümü tüketen barkod decode hattı ise PDF sayfalarından döndürülmüş QR kodlarını decode etmekte daha derin ele alınır. Motorunuz harici bir tanıyıcıyı sarıyorsa, aranabilir PDF için Tesseract OCR adaptörü aynı arayüzün süreç izolasyonu ile iptal tarafını gösterir
Hızlı başvuru: CropBox'a uygun koordinat eşleme
- Renderer görünür kutuyu — MediaBox'a kırpılmış CropBox'ı (ISO 32000-1 §14.11.2) — rasterize eder; her pixel-to-page eşlemesi o kutuyu,
GetLoadedPageVisibleBoxile okuyarak kullanmalıdır - HotPDF v2.770.153
ApplyLoadedOCRTextLayer'ı düzeltti; v2.770.154 bütün sayfalıkDecodeLoadedPageBarcodesileDetectLoadedRedactionFindings'ten gelen yüz bulgularını düzeltti; v2.766.64'ten o sürümlere kadarki derlemeler etkilenir THPDFOCRWordkutuları sol-üst kökenli bitmap pixel'leridir;GetLoadedPageBoxileGetLoadedPageVisibleBoxsol-alt kökenli,Bottom < Topolan PDF user space'i döndürür- Ölçek
DPI / 72'dir; onu DPI'dan türetin, bitmap genişliğine bölünmüş bir page box'tan asla - /Rotate hangi kenarların önemli olduğuna karar verir: 0 ile 90'da Left ile Bottom, 180'de Right ile Bottom, 270'te Right ile Top
- OCR kelimelerini pixel olarak döndürün ve eşlemeyi HotPDF'e bırakın; yalnızca kendi filtre mantığınız için çevirin
- Etkilenen bir derlemenin işlediği kırpılmış sayfalarda OCR'ı yeniden koşturun ve
SkipPagesWithText'in eski katmanı hâlihazırda taşıyan sayfaları atlattığını unutmayın
Burada kullanılan tanıma özellikleri, page-box sorguları ve yüklenen-belge rendering'i, Delphi ve C++Builder için HotPDF bileşeninde gelir; lisanslama, deneme indirmeleri ve tam özellik listesi HotPDF Delphi PDF component sayfasındadır