Powershell: Text anhand seiner Position aus PDF-Dokumenten auslesen
Inhaltsverzeichnis
Einleitung
Möglichkeiten für das automatische Extrahieren von Text aus PDF-Dokumenten gibt es etliche (PDFtk, iTextSharp,...). Das gezielte Extrahieren der gewünschten Inhalte geschieht dann aber oftmals mittels Regular Expressions, das genügt auch in vielen Fällen, so lange sich der Text ausreichend von anderen Teilen des Dokumentes eindeutig abhebt. Oftmals lässt aber die Struktur des Dokumentes ein effizientes auslesen der Texte nicht mehr zu, und genau hier soll folgende Funktion einspringen, indem man mit Ihr Text anhand seiner absoluten Position innerhalb eines genau definierten Rechtecks auslesen kann. Nützlich ist das bspw. bei immer gleich aufgebauten Formularen bei denen aber keine "echten" PDF-Formularfelder mehr vorhanden sind weil das PDF mit der "flatten" Funktion behandelt wurde. Wer dagegen echte PDF-Formulare bei denen die Formularfelder noch im Original vorhanden sind auslesen möchte findet von mir bereits hier ein entsprechendes Skript.
Was macht die Funktion
Das unten aufgeführte CMDLet extrahiert bereits vorhandenen maschinenlesbaren Text aus einem oder mehreren PDF-Dokumenten mittels seiner absoluten Positionen im Dokument. Anhand einer Hashtable definiert man die Koordinaten für Rechtecke innerhalb derer der Text ausgelesen werden soll. Als Ergebnis erhält man ein oder mehrere Objekte mit den Texten als separate Eigenschaften für eine objektorientierte Weiterverarbeitung. Für die Verarbeitung der PDF-Dokumente verwendet das Skript als Basis die iTextSharp-DLL welche automatisch von Nuget in das Skript- oder TEMP-Verzeichnis (bei direkter Ausführung ohne Skriptdatei) heruntergeladen wird sofern sie noch nicht vorhanden ist.
Powershell CMDLet: Get-PDFTextFromLocation
function Get-PDFTextFromLocation {
<#
.Synopsis
Extracts text from PDF documents
.DESCRIPTION
Extracts text from defined locations in PDF documents
.LINK
© @colinardo (https://administrator.de/tutorial/powershell-text-anhand-seiner-position-aus-pdf-dokumenten-auslesen-32779450015.html)
.INPUTS
System.IO.FileSystemInfo[] / You can pipe the output of Get-ChildItem to the function
.OUTPUTS
System.Management.Automation.PSCustomObject[]
.EXAMPLE
$locations = @{
1 = [ordered]@{
'Invoice No.' = 30,123,203,172
'Date' = 130,230,150,245
}
}
Get-ChildItem "E:\data" -File -Filter *.pdf | Get-PDFTextFromLocation -locations $locations
This example extracts 2 fields on page 1 from every PDF coming on the pipeline
.NOTES
Hashtable-Format for text location definition by page number:
- first level of hashtable defines the page index from which to extract the field data
- second level keys define a user defined unique names for the fields
- the value in the second level defines the rectangle location as an array with four items:
- [LowerLeftX],[LowerLeftY],[UpperRightX],[UpperRightY])
- coordinate origin is the non rotated lower left corner of the page.
Example 1 (extracts 2 fields from page 1):
============
@{
1 = [ordered]@{
'Invoice No.' = 30,123,203,172
'Date' = 130,230,150,245
}
}
Example 2 (extracts 2 fields from page 1, and 1 field from page 2):
============
@{
1 = [ordered]@{
'Invoice No.' = 30,123,203,172
'Date' = 130,230,150,245
}
2 = [ordered]@{
'Sum' = 120,80,150,100
}
}
# ---------------------------------------------
Hashtable-Format to select pages by regular expression (when the document contains multiple pages of the same format):
- first level of hashtable defines a regular expression pattern wich selects the pages to be processes
- second level keys define a user defined unique names for the fields
- the value in the second level defines the rectangle location as an array with four items:
- [LowerLeftX],[LowerLeftY],[UpperRightX],[UpperRightY])
- coordinate origin is the non rotated lower left corner of the page.
Example 3 (extracts 2 fields from each page wich matches the regular expression pattern '(?ism)Invoice'):
============
@{
'(?ism)Invoice' = [ordered]@{
'Invoice No.' = 30,123,203,172
'Date' = 130,230,150,245
}
}
#>
param(
# Filepath
[Parameter(mandatory=$true,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)][Alias('Fullname')][string[]]$path,
# hashtable with definition of pages and locations
[Parameter(mandatory=$true)][hashtable]$locations,
# optional match page content by Regex instead of page number
[Parameter(mandatory=$false)][switch]$UsePageSelectRegex,
# output source fileinfo
[Parameter(mandatory=$false)][switch]$passthru
)
begin{
# function to load iTextSharp assembly
function Load-iTextLibrary {
if($psscriptroot -ne ''){
$localpath = join-path $psscriptroot 'itextsharp.dll'
}else{
$localpath = join-path $env:TEMP 'itextsharp.dll'
}
$zip = $null;$tmp = ''
$script:iTextDLL = $localpath
try{
if(!(Test-Path $localpath)){
Add-Type -A System.IO.Compression.FileSystem
$tmp = "$env:TEMP\$([IO.Path]::GetRandomFileName())"
write-host "Downloading and extracting required 'iTextSharp.dll' ... " -F Green -NoNewline
(New-Object System.Net.WebClient).DownloadFile('https://www.nuget.org/api/v2/package/iTextSharp/5.5.13.1', $tmp)
$zip = [System.IO.Compression.ZipFile]::OpenRead($tmp)
$zip.Entries | ?{$_.Fullname -eq 'lib/itextsharp.dll'} | %{
[System.IO.Compression.ZipFileExtensions]::ExtractToFile($_,$localpath)
}
write-host "OK" -F Green
}
if (Get-Item $localpath -Stream zone.identifier -ea SilentlyContinue){
Unblock-File -Path $localpath
}
Add-Type -Path $localpath
}catch{
throw "Error: $($_.Exception.Message)"
}finally{
if ($zip){$zip.Dispose()}
if ($tmp -ne ''){del $tmp -Force -EA SilentlyContinue}
}
}
# load iText library
Load-iTextLibrary
# also read protected files
[iTextSharp.text.pdf.PdfReader]::unethicalreading = $true
if (!("iText.GetTextFromLocationStrategy" -as [type])){
# Create GetTextFromLocationStrategy class
Add-Type -ReferencedAssemblies $script:iTextDLL -TypeDefinition '
using System.Text;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using iTextSharp.text;
using iTextSharp.text.pdf.parser;
using iTextSharp.text.pdf;
namespace iText {
public class GetTextFromLocationStrategy : iTextSharp.text.pdf.parser.ITextExtractionStrategy {
StringBuilder result = new StringBuilder();
OrderedDictionary resultfields = new OrderedDictionary();
Vector lastBaseline = new Vector(0, 0, 0);
OrderedDictionary rects;
public GetTextFromLocationStrategy(OrderedDictionary locations) {
rects = locations;
}
public void RenderText(TextRenderInfo renderinfo) {
Vector curBaseline = renderinfo.GetBaseline().GetStartPoint();
Vector topRight = renderinfo.GetAscentLine().GetStartPoint();
iTextSharp.text.Rectangle rect = new iTextSharp.text.Rectangle(curBaseline[Vector.I1], curBaseline[Vector.I2], topRight[Vector.I1], topRight[Vector.I2]);
foreach (DictionaryEntry field in rects) {
Rectangle fieldRect = (Rectangle)field.Value;
if (!resultfields.Contains(field.Key)) {
resultfields.Add(field.Key, new StringBuilder(""));
}
if (rect.Left >= fieldRect.Left && rect.Top <= fieldRect.Top && rect.Bottom >= fieldRect.Bottom && rect.Right <= fieldRect.Right) {
if (lastBaseline[Vector.I2] > curBaseline[Vector.I2] && ((StringBuilder)resultfields[field.Key]).ToString() != "") {
((StringBuilder)resultfields[field.Key]).Append("\r\n");
result.Append("\r\n");
}
string txt = renderinfo.GetText();
((StringBuilder)resultfields[field.Key]).Append(txt);
result.Append(txt);
}
}
lastBaseline = curBaseline;
}
public string GetResultantText() {
return result.ToString();
}
public OrderedDictionary GetResultTable() {
OrderedDictionary dic = new OrderedDictionary();
foreach (DictionaryEntry field in resultfields) {
dic.Add(field.Key, field.Value.ToString());
}
return dic;
}
public void BeginTextBlock() { }
public void EndTextBlock() { }
public void RenderImage(ImageRenderInfo renderinfo) { }
}
}
'
}
# function to convert mm to points
function M2P($mm){$mm * 2.83465}
# convert user supplied location values to rectangles with point values
foreach($page in $locations.Keys){
[string[]]$locations.$page.Keys | %{$locations.$page.$_ = [iTextSharp.text.Rectangle]::new((M2P $locations.$page.$_[0]),(M2P $locations.$page.$_[1]),(M2P $locations.$page.$_[2]),(M2P $locations.$page.$_[3]))}
}
}
process{
# Process files
foreach($file in $path) {
$reader = $null
try {
write-verbose "Processing file '$($file)' ..."
# create pdf reader object
$reader = New-Object iTextSharp.text.pdf.PdfReader $file
# parse pages defined
if ($UsePageSelectRegex.IsPresent){
$pages = 1..($reader.NumberOfPages) | ?{[iTextSharp.text.pdf.parser.PdfTextExtractor]::GetTextFromPage($reader,$_) -match $locations.Keys[0]}
$data = foreach($page in $pages){
# create instance from custom Text-Extraction-Strategy
$strategy = New-Object iText.GetTextFromLocationStrategy ([System.Collections.Specialized.OrderedDictionary]($locations.([string]$locations.Keys[0])))
# execute extraction
[void][iTextSharp.text.pdf.parser.PdfTextExtractor]::GetTextFromPage($reader,$page,$strategy)
# output result object
[pscustomobject]$strategy.GetResultTable()
}
}else{
$data = [ordered]@{}
$pages = $locations.Keys | ?{$_ -in (1..$reader.NumberOfPages)}
foreach($page in $pages){
# create instance from custom Text-Extraction-Strategy
$strategy = New-Object iText.GetTextFromLocationStrategy ([System.Collections.Specialized.OrderedDictionary]$locations.$page)
# execute extraction
[void][iTextSharp.text.pdf.parser.PdfTextExtractor]::GetTextFromPage($reader,$page,$strategy)
# append to result object
$data += $strategy.GetResultTable()
}
if ($passthru.IsPresent){
# append file info
$data.FileInfo = Get-Item $file
}
$data = [pscustomobject]$data
}
# output data
$data
}catch{
write-host $_.Exception.Message -F Red
}finally{
if($reader){$reader.Dispose()}
}
}
}
}
Praktische Verwendung der Funktion
Gegeben sei als Beispiel das folgende Muster-Dokument aus dem wir die zwei umrandeten Textfelder extrahieren werden:
Wie definiert man die Positionen?
Für die Definition der Positionen dient eine Hashtable die ein bestimmtes Format aufweisen muss. Auf der ersten Ebene der Hashtable wird die Seitenzahl als Key angegeben auf die sich die folgenden Positionen beziehen. Der Wert des Keys ist dann eine weitere Hashtable die als Keys eindeutige Bezeichner haben und als Wert ein Array aus 4 Zahlen hat, welches ein Rechteck über die Position des Textes legt der darin extrahiert werden soll. Das Rechteck bzw. das Array ist wie folgt definiert:
[LOWER-LEFT-X] , [LOWER-LEFT-Y] , [UPPER-RIGHT-X] , [UPPER-RIGHT-Y]
- Der Koordinaten-Ursprung liegt dabei immer in der linken unteren Ecke der jeweiligen Seite.
- Als Einheit kommen Millimeter (mm) zum Einsatz
Das ganze sieht dann in der Praxis folgendermaßen aus.
Hier werden zwei Felder auf Seite 1 ausgelesen:
$locations = @{
1 = [ordered]@{
'Geschäftspartnernummer' = 167,223,188,228
'Empfänger' = 23,211,73,233
}
}
Es können sowohl mehrere Seiten als auch pro Seite mehrere Positionen in der Hashtable definiert werden. Die Namen der Felder können beliebig gewählt werden, müssen aber alle eindeutig sein damit später eine eindeutige Zuordnung möglich ist.
Hier werden zwei Felder auf Seite 1 und 1 Feld auf Seite 3 ausgelesen.
$locations = @{
1 = [ordered]@{
'Geschäftspartnernummer' = 167,223,188,228
'Empfänger' = 23,211,73,233
}
3 = [ordered]@{
'Bankleitzahl' = 10,20,40,30
}
}
Eine weitere Möglichkeit der Funktion bietet der Switch-Parameter -UsePageSelectRegex. Dieser ist für das Szenario gedacht bei dem man in einem Dokument mehrere gleich aufgebaute Seiten vorliegen. Er verändert das Verhalten der Funktion in der Art, dass man nun in der Hashtable auf der ersten Ebene als Key statt fester Seitennummern einen Regular Expression Pattern hinterlegt, welcher die Seiten anhand eines Musters auswählt welches auf den gewünschten Seiten immer im Text auftaucht. Die definierten Positionen werden dann aus jeder zutreffende Seite des PDFs ausgelesen.
Anwendungsbeispiel bei Verwendung des Parameters -UsePageSelectRegex :
Hier werden zwei Felder für jede Seite des PDFs ausgelesen auf welcher das Regular Expression Pattern 'Abschlagsänderung' zutrifft
$locations = @{
'Abschlagsänderung' = [ordered]@{
'Geschäftspartnernummer' = 167,223,188,228
'Empfänger' = 23,211,73,233
}
}
Wichtig ist das die Koordinaten-Angaben mit ausreichend Abstand zu dem Text umgebenden Rahmen definiert werden, ansonsten wird der Text darin nicht extrahiert! Erfasst werden nämlich nur Blöcke deren Koordinaten wirklich vollständig innerhalb des vom Benutzer definierten Rechtecks liegen. Sollte also ein Text einmal nicht vollständig oder überhaupt nicht ausgelesen werden, ist meist das Rechteck zu klein gewählt worden, es hilft dann die Koordinaten des Rechtecks zu vergrößern, dabei macht es nichts aus wenn das Rechteck zum Teil anderen Text ansatzweise umfasst so lange dessen umgebender Kasten nicht durch das Rechteck mit erfasst werden.
Für die Bestimmung der exakten Positionen können als digitale Hilfsmittel in PDF-Readern oder Editoren etwa vorhandene Mess-Tools bzw. Lineal-Funktionen benutzt werden. Ein extra Ausdrucken des Dokumentes und Messen mittels Lineal ist somit überflüssig und sollte auch aus ökologischen Gründen vermieden werden wann immer es geht 🌲.
Es folgen nun ein paar praktische Beispiele für die Verwendung des CMDLets.
Code-Beispiel 1 - Auslesen eines einzelnen Dokumentes
# Positionen der zu extrahierenden Textfelder definieren
$locations = @{
1 = [ordered]@{
'Geschäftspartnernummer' = 167,223,188,228
'Empfänger' = 23,211,73,233
}
}
# Ein einzelnes PDF-Dokumente auslesen und das Ergebnis auf der Konsole als Liste ausgeben
Get-PDFTextFromLocation -Path "E:\dokument.pdf" -locations $locations | format-list *
Ergebnis für das o.g. Musterdokument
Code-Beispiel 2 - Export der Daten mehrerer gleich aufgebauter Dokumente in eine CSV-Datei
# Positionen der zu extrahierenden Textfelder definieren
$locations = @{
1 = [ordered]@{
'Geschäftspartnernummer' = 167,223,188,228
'Empfänger' = 23,211,73,233
}
}
# Alle gleich aufgebauten PDF-Dokumente eines Ordners verarbeiten und das Ergebnis in eine CSV-Datei ausgeben
Get-ChildItem "E:\quelldaten" -File -Filter *.pdf | Get-PDFTextFromLocation -locations $locations | export-csv "E:\output.csv" -Delimiter ";" -NoTypeInformation -Encoding UTF8 -Force
Ergebnis der CSV-Datei für zwei ähnliche Musterdokumente
"Geschäftspartnernummer";"Empfänger"
"1222334455";"Herrn
Max Mustermann
Musterstr. 99
99999 Musterhausen"
"22342344";"Frau
Monika Musterfrau
Musterweg. 1
88888 Musterdorf"
Code-Beispiel 3 - Verwendung des Parameters -UsePageSelectRegex für den Export mehrfach gleich aufgebauter Seiten einer PDF-Datei in eine CSV-Datei
# Regular Expression Pattern der auszuwählenden Seiten und Positionen der zu extrahierenden Textfelder definieren
$locations = @{
'(?i)Abschlagsänderung' = [ordered]@{
'Geschäftspartnernummer' = 167,223,188,228
'Empfänger' = 23,211,73,233
}
}
# PDF-Dokument auslesen und das Ergebnis in eine CSV-Datei ausgeben
Get-PDFTextFromLocation -Path "E:\dokument.pdf" -locations $locations -UsePageSelectRegex | export-csv "E:\output.csv" -Delimiter ";" -NoTypeInformation -Encoding UTF8 -Force
Eventuell ist das dem ein oder anderen von euch ein nützliches Hilfsmittel.
Wie immer alle Angaben ohne Gewähr auf Leib und Leben .
Bitte markiere auch die Kommentare, die zur Lösung des Beitrags beigetragen haben
Content-ID: 32779450015
Url: https://administrator.de/contentid/32779450015
Ausgedruckt am: 18.11.2024 um 17:11 Uhr
9 Kommentare
Neuester Kommentar
Hallo,
danke für diesen Ansatz hab vor längeren auch was gebaut. Aber nicht ganz so schön.
Hatte mir als Basis pdftotext ausgesucht- https://www.xpdfreader.com/pdftotext-man.html
Hintergrund war, dass man hier schon Layout übernehmen oder unterdrüken kann. Mit Layout wäre es so, wie wir es von früher kennen: Fixe Abstände zum linken Rand. Zumindest so einigermaßen.
Die Ankerpunkte kann man via Regex auch schön finden. Hier ging es darum, wenn ein Sachbearbeiter nichht gefüllt war, dass dann trotzdem das richtige Feld gefunden wurde.
Muster:
Die Belge stammen aus Crystal Reports. Man kan natürlich auch damit einen "hidden" String - also weiße Schrift auf weißen Grund o.ä. - nehmen und alles in eine Art Token packen.
Das war damals mein Ansatz. Lief aber relativ robust.
@colinardo ansonsten schwebte mir vor Dokument und Text-Stream erst zu slicen und mit tesseract auszuelesen. Weiter unten ein Bsp. dazu für einen Barcode. Zwar kein OCR, aber so die Richtung.
An pdftotext wird wohl nicht wirklich viel weiterentwickelt. Schaue mir daher dein Script mal genauer an. Mir fällt schon dass ein oder andere Projekt ein.
Wir haben ja X-Rechung, ZUGFeRD etc. aber ohne XML Anteil ist so ein PDF dumm wie Stück Brot. Ich mag immer noch den Ansatz mit der Zerlegung.
Die Trefferquote war recht hoch. Ansonsten hab ich einfach "MANUELLE ERFASSUNG" o.ä. in den Dateinamen geschrieben. Dann konnte jeder einfach nacharbeiten. Kam aber selten vor.
Mit Ausschnitten wie beim Barcode lässt sich das ganze noch verfeinern. Sieht man ja an deinen Beispiel gut. Abbyy oder Omnipage machen ja nichts anderes. Nur das man hier die Masken über die GUI definieren kann.
Nimmt man das noch via Script vor, steht eine solche Lösung den kommerziellen kaum in was nach.
Barcode ausschneiden und aulsesen:
danke für diesen Ansatz hab vor längeren auch was gebaut. Aber nicht ganz so schön.
Hatte mir als Basis pdftotext ausgesucht- https://www.xpdfreader.com/pdftotext-man.html
Hintergrund war, dass man hier schon Layout übernehmen oder unterdrüken kann. Mit Layout wäre es so, wie wir es von früher kennen: Fixe Abstände zum linken Rand. Zumindest so einigermaßen.
Die Ankerpunkte kann man via Regex auch schön finden. Hier ging es darum, wenn ein Sachbearbeiter nichht gefüllt war, dass dann trotzdem das richtige Feld gefunden wurde.
Muster:
A U F T R A G S B E S T Ä T I G U N G
Ihr Ansprechpartner: Kunden-Nr. Auf.-Nr. Datum Seite
Max Mustermann 22292 864987 /V 11.02.21 1
Die Belge stammen aus Crystal Reports. Man kan natürlich auch damit einen "hidden" String - also weiße Schrift auf weißen Grund o.ä. - nehmen und alles in eine Art Token packen.
Das war damals mein Ansatz. Lief aber relativ robust.
@colinardo ansonsten schwebte mir vor Dokument und Text-Stream erst zu slicen und mit tesseract auszuelesen. Weiter unten ein Bsp. dazu für einen Barcode. Zwar kein OCR, aber so die Richtung.
An pdftotext wird wohl nicht wirklich viel weiterentwickelt. Schaue mir daher dein Script mal genauer an. Mir fällt schon dass ein oder andere Projekt ein.
Wir haben ja X-Rechung, ZUGFeRD etc. aber ohne XML Anteil ist so ein PDF dumm wie Stück Brot. Ich mag immer noch den Ansatz mit der Zerlegung.
Die Trefferquote war recht hoch. Ansonsten hab ich einfach "MANUELLE ERFASSUNG" o.ä. in den Dateinamen geschrieben. Dann konnte jeder einfach nacharbeiten. Kam aber selten vor.
Mit Ausschnitten wie beim Barcode lässt sich das ganze noch verfeinern. Sieht man ja an deinen Beispiel gut. Abbyy oder Omnipage machen ja nichts anderes. Nur das man hier die Masken über die GUI definieren kann.
Nimmt man das noch via Script vor, steht eine solche Lösung den kommerziellen kaum in was nach.
# Snippet
$pdftotext = "C:\ps\pdftotext.exe"
Function Global:FindBelegNr($PDFToTextFile)
{
#PDF in durchsuchbaren Text umwandeln. simple2 und clip zum "normalisieren"
Start-Process -NoNewWindow -Wait $pdftotext -ArgumentList "-simple2", "-clip", "-f 1", "-l 1", $PDFToTextFile, $TempTextFile
$texttest = Get-Content -Path $TempTextFile -Raw
#$patternBelegRe = "Rech.-Nr. "
#$patternBelegAb = "Auf.-Nr. "
[string] $DokBelegAbRe =""
[string] $DokBelegAn =""
[string] $DokBelegLs =""
#Angebot Nr in einer Zeile
$regex_An = "(ANGEBOT )(Kunden-Nr\. )(Angebot-Nr\. )(Datum )(Seite)\r\n([\d]+)\s([\d]+)"
#Auf ODER Rech übermehrere Zeilen
$regex_AbRe = "(.*)\r\n(Ihr Ansprechpartner: )(Kunden-Nr\. )((Auf|Rech)\.-Nr\. )(Datum )(Seite)\r\n(.*)\s([\d]+)\s([\d]+)"
#Nur Lieferschein
$regex_Ls = "(LIEFERSCHEIN)\r\n(Lieferschein-Nr\. )(Lieferanten-Nr\. )(Datum )(Seite)\r\n([\d]+)\s([\d]+)"
#Nur Bestellschein
$regex_Bs = "(.*)(Lieferanten-Nr. )(Bestell-Nr. )(Datum )(Seite)\r\n([\d]+)\s([\d]+)"
$texttest | select-string -Pattern $regex_AbRe -AllMatches | % { $DokBelegAbRe = $_.Matches.Groups[1].Value + "_" + $_.Matches.Groups[10].Value }
$texttest | select-string -Pattern $regex_An -AllMatches | % { $DokBelegAn = $_.Matches.Groups[1].Value + "_" + $_.Matches.Groups[7].Value }
$texttest | select-string -Pattern $regex_Ls -AllMatches | % { $DokBelegLs = $_.Matches.Groups[1].Value + "_" + $_.Matches.Groups[6].Value }
$texttest | select-string -Pattern $regex_Bs -AllMatches | % { $DokBelegBs = $_.Matches.Groups[1].Value + "_" + $_.Matches.Groups[7].Value }
IF ($DokBelegAbRe.length -gt 0) { $Script:Belegnr = $DokBelegAbRe }
IF ($DokBelegAn.length -gt 0) { $Script:Belegnr = $DokBelegAn }
IF ($DokBelegLs.length -gt 0) { $Script:Belegnr = $DokBelegLs }
IF ($DokBelegBs.length -gt 0) { $Script:Belegnr = $DokBelegBs }
}
Barcode ausschneiden und aulsesen:
using namespace System.Drawing;
Add-Type -AssemblyName System.Drawing;
[void] [Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
$testBild = "C:\temp\code_ls4.png-1.png"
$src=[System.Drawing.Image]::FromFile($testBild)
$rect = New-Object System.Drawing.Rectangle(740,1500,400,250) # top, left, width, height of slice
$slice = $src.Clone($rect, $src.PixelFormat);
#$slice.Save("c:\temp\test_slice.png", "png");
$src = $slice;
[void] [System.Reflection.Assembly]::LoadFrom("c:\temp\BarcodeImaging.dll");
$barcodes = @{}
[BarcodeImaging]::FullScanPage([ref] $barcodes, $src, 150)
$barcodes
Zitat von @colinardo:
Danke dir. Dachte das taucht hier ja immer wieder mal auf, und gefunden habe ich, speziell das, bisher bis auf Tabula(für Tabellen) nur in kommerziell verfügbaren Programmen.
Danke dir. Dachte das taucht hier ja immer wieder mal auf, und gefunden habe ich, speziell das, bisher bis auf Tabula(für Tabellen) nur in kommerziell verfügbaren Programmen.
Gebe dir vollkommen recht! Andere nennen es DocuXXX und verlangen 60.000 bis 120.000 Euro. Workflows und DSGVO mal außen vor gelassen braucht man meist doch im Alltag nur ein paar Infos.
Wenn man so die Datei umbennennt, reicht dass bei manchen DMS schon für einen Import. Die Möglichkeiten die sich damit auftuen sind vielflältig.
Fehlt der Text hilft tesseract weiter. Auch wenn es nur um Ausschnitte - s.o. geht.
Aus eigener Praxis kann ich auf jedenfall bestätigen, dass @colinardo Lösung sehr robust ist! Arbeitet es doch teils so, wie Menschen es auch händisch umsetzen würden.
Passt es mal nicht, kann man immer noch die Suchmuster verfeienern. Auf fehlen von Nummern reagieren etc. Aber sowas im Detail durch zu spielen sollte jeden klar sein. Dann hat man ein sehr robustes System!
Ich kenne es auch aus kommerziellen Systemen als ZoneOCR.
Sehr sehr ähnlich aufgemacht ist es bei ScannerVision.
Aber halt closed Source und bis auf das Thema der Position des Bereiches ist dort nicht viel zu machen.
Und natürlich die Definition der dort erhobenen Daten und das ein OCR gleich mitgemacht wird...
Aber Colinardo hat dies vorgemacht wie man es selbst bauen kann.
Genial !
Sehr sehr ähnlich aufgemacht ist es bei ScannerVision.
Aber halt closed Source und bis auf das Thema der Position des Bereiches ist dort nicht viel zu machen.
Und natürlich die Definition der dort erhobenen Daten und das ein OCR gleich mitgemacht wird...
Aber Colinardo hat dies vorgemacht wie man es selbst bauen kann.
Genial !
Danke, mir hat es jmd in einen Beitrag eingehaengt Link. Ich lerne gerade ueber Funktionen, habe es in ein Modul kopiert und erhalte einen Fehler, es will einen Namen fuer ein Makro haben. Habe gestern gelernt, dass die Funktion eine Klammer () am Ende haben soll. Liegt es daran?
Eine Frage geht es ueberhaupt fuer meine Zwecke?
Habe knapp 500 pdf's mit bis zu 90 oder mehr Seiten. Bestellungen koennen bei einem Kunden bis zu vier auf einer Seite sein, bei zwei Kunden entweder 1/1 oder 1/2 oder 2/1 Bestellungen, also beide je eine oder der erste zwei und der zweite eine und umgekehrt. Die Felder koennte ich jeweils messen und eingeben mit allen Kombinationen, aber nicht fuer jede Seite oder verstehe ich es falsch?
Eine Frage geht es ueberhaupt fuer meine Zwecke?
Habe knapp 500 pdf's mit bis zu 90 oder mehr Seiten. Bestellungen koennen bei einem Kunden bis zu vier auf einer Seite sein, bei zwei Kunden entweder 1/1 oder 1/2 oder 2/1 Bestellungen, also beide je eine oder der erste zwei und der zweite eine und umgekehrt. Die Felder koennte ich jeweils messen und eingeben mit allen Kombinationen, aber nicht fuer jede Seite oder verstehe ich es falsch?
Ich hab hier eig. auch zu viel geschrieben. Rubrik: Anleitungen ....
Lass es doch drüben diskutieren! Hab auch viel Sauereien gemacht. Nur das sprengt hier den Rahmen.
Serie: Powershell PDF
Powershell: Text anhand seiner Position aus PDF-Dokumenten auslesen9