Feedback, love to HS and SP
This commit is contained in:
@@ -1,14 +1,14 @@
|
||||
\section{Analyse}
|
||||
\label{analyse}
|
||||
|
||||
Nachdem die vorbereiteten Bilddaten an das Texterkennungssystem gemäß \autoref{implementierung} übergeben und die Ergebnisse ermittelt wurden, werden die extrahierten Textdaten nun mit den manuell erstellten "Soll-Daten" verglichen. Anhand der Statistik kann festgestellt werden, welche Vorgehensweise zu der besten Qualität führt. Um die Ergebnisse zu visualisieren, erstellt der in \autoref{components_reportgenerator} beschriebene "ReportGenerator" auf Basis der Bilddateinamen automatisch einen Bericht mit den Vergleichsdaten in Tabellenform. Für eine bessere Lesbarkeit wird der erstellte Bericht in verschiedene Kategorien unterteilt.
|
||||
Nachdem die vorbereiteten Bilddaten an das Texterkennungssystem gemäß \autoref{implementierung} übergeben und die Ergebnisse ermittelt wurden, werden die extrahierten Textdaten nun mit den manuell erstellten "Soll-Daten" verglichen. Anhand der Statistik kann festgestellt werden, welche Vorgehensweise zu der besten Qualität führt. Um die Ergebnisse zu visualisieren, erstellt der in \autoref{components_reportgenerator} beschriebene "ReportGenerator" auf Basis der Bilddateinamen automatisch einen Bericht mit den Vergleichsdaten in Tabellenform. Der erstellte Bericht wird in verschiedene Kategorien unterteilt.
|
||||
|
||||
\subsection*{Vergleich im Detail}
|
||||
\subsection{Vergleich im Detail}
|
||||
\label{report_detailed}
|
||||
|
||||
\subsubsection*{Processor Stats}
|
||||
\subsubsection{Processor Stats}
|
||||
|
||||
In der Sektion "Processor Stats", siehe \autoref{tbl:report_detailed_processorstats}, wird die Gesamtwortfehlerrate pro Bild mit dem jeweiligen Prozessor in Verhältnis gesetzt. Auch wenn es Bilder gibt, die aufgrund ihrer Eigenschaften (niedrige Auflösung, schwierige Farbgebung, etc.) von allen Prozessoren gleichermaßen schlecht erkannt werden, fallen bei gewissen Verfahren Spezialfälle auf. So werden beispielsweise die Textdaten in den Dialog-Knöpfen des Bilds "worldview\_zoom\_steps\_001" Dank eines falsch gewählten Schwellenwerts unkenntlich gemacht. Die Texterkennung schlägt fehl.
|
||||
In der Sektion "Processor Stats", siehe \autoref{tbl:report_detailed_processorstats}, wird die Gesamtwortfehlerrate pro Bild mit dem jeweiligen Prozessor in Verhältnis gesetzt. Anhand der Metriken ist zu erkennen, dass gewisse Bilder aufgrund ihrer Eigenschaften (niedrige Auflösung, schwierige Farbgebung, etc.) von allen Prozessoren gleichermaßen schlecht erkannt werden. Jedoch fallen auch Spezialfälle auf: So werden beispielsweise die Textdaten in den Dialog-Knöpfen des Bilds "worldview\_zoom\_steps\_001" Dank eines falsch gewählten Schwellenwerts unkenntlich gemacht. Die Texterkennung schlägt fehl.
|
||||
|
||||
\begin{table}[!ht]
|
||||
\centering
|
||||
@@ -17,7 +17,7 @@ In der Sektion "Processor Stats", siehe \autoref{tbl:report_detailed_processorst
|
||||
\label{tbl:report_detailed_processorstats}
|
||||
\end{table}
|
||||
|
||||
\subsubsection*{Scan Results}
|
||||
\subsubsection{Scan Results}
|
||||
|
||||
Die Sektion "Scan Results" bildet den Abschluss des Detailvergleichs. Hier werden alle Verfahrenskombinationen einzeln und mit allen verfügbaren Daten aufgeführt.
|
||||
|
||||
@@ -35,10 +35,10 @@ Die Sektion "Scan Results" bildet den Abschluss des Detailvergleichs. Hier werde
|
||||
\label{tbl:report_detailed_scanresults_words}
|
||||
\end{table}
|
||||
|
||||
\subsection*{Prozessoren im Überblick}
|
||||
\subsection{Prozessoren im Überblick}
|
||||
\label{report_processingsummary}
|
||||
|
||||
Neben dem Detailvergleich beinhaltet der generierte Bericht auch die "Processing Summary". Diese Kategorie zeigt eine vereinfachte Übersicht aller Ergebnisse. Je nach Rubrik wird jeweils der Median \bzw Durchschnitt der \hyperref[metriken_cer]{Character Error Rate} und \hyperref[metriken_wer]{Word Error Rate} berechnet.
|
||||
Neben dem Detailvergleich beinhaltet der generierte Bericht auch die "Processing Summary". Diese Kategorie zeigt eine kurze Übersicht aller Ergebnisse. Je nach Rubrik wird jeweils der Median \bzw Durchschnitt der \hyperref[metriken_cer]{Character Error Rate} und \hyperref[metriken_wer]{Word Error Rate} berechnet.
|
||||
|
||||
Auf Basis der Daten in \autoref{tbl:report_summary_wer}, \autoref{tbl:report_summary_cer} und \autoref{tbl:report_summary_time} lässt sich der Gesamterfolg der Bildvorbereitung \bzw der darauf folgenden Filterung feststellen.
|
||||
|
||||
|
||||
@@ -3,30 +3,31 @@
|
||||
|
||||
\subsection{Vergleichsdaten}
|
||||
|
||||
Als Ausgangsdaten für die Durchführung wurde eine zufällige Auswahl an Dokumentationsscreenshots getroffen. Zusätzlich wurden auch Bilder in die Stichprobe mit aufgenommen, die beispielsweise aufgrund ihrer Auflösung oder Kontrastverhältnisse schwer lesbar sind.
|
||||
Als Ausgangsdaten für die Durchführung wurde eine zufällige Auswahl an Dokumentationsscreenshots, wie in \autoref{fig:screenshot_beispiel_gut} abgebildet, getroffen. Zusätzlich wurden auch Bilder wie in \autoref{fig:screenshot_beispiel_schlecht} in die Stichprobe mit aufgenommen, die beispielsweise aufgrund ihrer Auflösung oder Kontrastverhältnisse schwer lesbar sind.
|
||||
|
||||
\begin{figure}[ht]
|
||||
\centering
|
||||
\fbox{\includegraphics[width=.7\textwidth]{include/screenshots/driver_SEL_Options_002.png}}
|
||||
\caption{Ein gut für die Texterkennung geeigneter Screenshot. Die wesentlichen Inhalte weisen einen guten Kontrast zum Hintergrund auf und befinden sich in Bereichen mit gleichmäßiger Helligkeit.}
|
||||
\label{}
|
||||
\label{fig:screenshot_beispiel_gut}
|
||||
\end{figure}
|
||||
|
||||
\begin{figure}[ht]
|
||||
\centering
|
||||
\fbox{\includegraphics[width=.7\textwidth]{include/screenshots/editor_startpage_project-exist_001.png}}
|
||||
\caption{Ein schlecht lesbarer Screenshot. Aufgrund der vielen Symbole und der bunten Flächen stellt dieses Bild eine Herausforderung für das Texterkennungssystem dar.}
|
||||
\label{}
|
||||
\label{fig:screenshot_beispiel_schlecht}
|
||||
\end{figure}
|
||||
|
||||
Die textuellen Inhalte aller ausgewählten Bilder wurden anschließend manuell extrahiert und für den programmatischen Vergleich in einer Datei im selben Verzeichnis wie die Quellbilddatei festgehalten.
|
||||
Die textuellen Inhalte aller ausgewählten Bilder wurden anschließend manuell extrahiert und für den programmatischen Vergleich in einer Datei im selben Verzeichnis wie die Quellbilddatei festgehalten. Ein Beispiel dafür ist der Screenshot "zrs\_ZAMS\_filter-alarmgroup\_001" in \autoref{fig:screenshot_verschlagwortet}, welches insgesamt 15 verschiedene Wörter beinhaltet.
|
||||
|
||||
\begin{figure}[ht]
|
||||
\centering
|
||||
\begin{minipage}{0.5\textwidth}
|
||||
\includegraphics[width=\textwidth]{include/screenshots/zrs_ZAMS_filter-alarmgroup_001.png}
|
||||
\includegraphics[width=0.99\textwidth]{include/screenshots/zrs_ZAMS_filter-alarmgroup_001.png}
|
||||
\end{minipage}
|
||||
\hspace{0.1\textwidth}
|
||||
\begin{minipage}{0.4\textwidth}
|
||||
\hfill
|
||||
\begin{minipage}{0.45\textwidth}
|
||||
\lstinputlisting[numbers=none]{include/screenshots/zrs_ZAMS_filter-alarmgroup_001.json}
|
||||
\end{minipage}
|
||||
\caption{Ein Screenshot und die daraus manuell extrahierten Schlagworte.}
|
||||
@@ -49,104 +50,105 @@ In der prototypischen Implementierung, geschrieben in der Programmiersprache \cs
|
||||
\subsubsection{Verarbeitungsketten}
|
||||
\label{components_processorchain}
|
||||
|
||||
Beim Entwurf des Verarbeitungssystems für die unterschiedlichen Bild- und Textverarbeitungsschritte wurde bewusst auf Flexibilität geachtet. Mithilfe von Interfaces und Builder-Methoden ist es möglich, Verarbeitungsschritte als Prozessoren (\engl{Processors}) zu definieren. So stellt beispielsweise das Normalisieren eines durch Tesseract erkannten Wortes einen Verarbeitungsschritt dar.
|
||||
Beim Entwurf des Verarbeitungssystems für die unterschiedlichen Bild- und Textverarbeitungsschritte wurde bewusst auf Flexibilität geachtet. Mithilfe von Interfaces und Builder-Methoden ist es möglich, Verarbeitungsschritte gemäß Programm \ref{prg:processor_interface} als Prozessoren (\engl{Processors}) zu definieren. So stellt beispielsweise das Normalisieren eines durch Tesseract erkannten Wortes einen Verarbeitungsschritt dar, wie in Programm \ref{prg:processor_implementation} gezeigt.
|
||||
|
||||
\begin{program}[!ht]
|
||||
\begin{CsCode}[numbers=none]
|
||||
public interface IProcessor
|
||||
{
|
||||
IEnumerable Process(IEnumerable inputs);
|
||||
}
|
||||
public interface IProcessor
|
||||
{
|
||||
IEnumerable Process(IEnumerable inputs);
|
||||
}
|
||||
|
||||
public interface IProcessor<in TInput, out TOutput> : IProcessor
|
||||
{
|
||||
IEnumerable<TOutput> Process(IEnumerable<TInput> inputs);
|
||||
public interface IProcessor<in TInput, out TOutput> : IProcessor
|
||||
{
|
||||
IEnumerable<TOutput> Process(IEnumerable<TInput> inputs);
|
||||
|
||||
new IEnumerable Process(IEnumerable inputs)
|
||||
{
|
||||
return Process((IEnumerable<TInput>)inputs);
|
||||
}
|
||||
new IEnumerable Process(IEnumerable inputs)
|
||||
{
|
||||
return Process((IEnumerable<TInput>)inputs);
|
||||
}
|
||||
}
|
||||
\end{CsCode}
|
||||
\caption{Auszug aus Datei "IProcessor.cs": Schnittstelle eines Prozessors.}
|
||||
\label{program:processor_interface}
|
||||
\label{prg:processor_interface}
|
||||
\end{program}
|
||||
|
||||
\begin{program}[!ht]
|
||||
\begin{CsCode}[numbers=none]
|
||||
public class ToLowerProcessor
|
||||
public class ToLowerProcessor
|
||||
: Processor<ScanResult, ScanResult>
|
||||
{
|
||||
public override IEnumerable<ScanResult> Process(
|
||||
IEnumerable<ScanResult> inputs
|
||||
)
|
||||
{
|
||||
public override IEnumerable<ScanResult> Process(
|
||||
IEnumerable<ScanResult> inputs
|
||||
)
|
||||
{
|
||||
foreach (var kv in inputs)
|
||||
{
|
||||
foreach (var kv in inputs)
|
||||
{
|
||||
kv.Word.Text = kv.Word.Text.ToLower();
|
||||
yield return kv;
|
||||
}
|
||||
kv.Word.Text = kv.Word.Text.ToLower();
|
||||
yield return kv;
|
||||
}
|
||||
}
|
||||
}
|
||||
\end{CsCode}
|
||||
\caption{Auszug aus Datei "ToLowerProcessor.cs": Normalisieren als einzelner Verarbeitungsschritt}
|
||||
\label{program:processor_implementation}
|
||||
\caption{Auszug aus Datei "ToLowerProcessor.cs": Normalisieren als einzelner Verarbeitungsschritt.}
|
||||
\label{prg:processor_implementation}
|
||||
\end{program}
|
||||
|
||||
Sollen mehrere Schritte verbunden werden, bietet das Processing-Framework die Möglichkeit, eine sogenannte Verarbeitungskette aufzubauen. Hier können Delegates oder komplette Prozessoren dynamisch als einzelne Schritte aneinandergereiht werden, wobei die Typensicherheit durch das generische Typensystem von \csharp stets gewahrt wird:
|
||||
Sollen mehrere Schritte verbunden werden, bietet das Processing-Framework die Möglichkeit, eine Verarbeitungskette aufzubauen. Gemäß der Schnittstellendefinition in Programm \ref{prg:processor_chain_interface} können Delegates oder komplette Prozessoren dynamisch als einzelne Schritte aneinandergereiht werden. Die Typensicherheit wird durch das generische Typensystem von \csharp stets gewahrt.
|
||||
|
||||
\begin{program}[!ht]
|
||||
\begin{CsCode}[numbers=none]
|
||||
public interface IProcessorChainConfiguration<TInput, TOutput> : IProcessorChain<TInput, TOutput>
|
||||
{
|
||||
IProcessorChainConfiguration<T, TOutput, TInput, TOutput> Use<T>(
|
||||
IProcessor<TInput, T> processor);
|
||||
public interface IProcessorChainConfiguration<TInput, TOutput>
|
||||
: IProcessorChain<TInput, TOutput>
|
||||
{
|
||||
IProcessorChainConfiguration<T, TOutput, TInput, TOutput> Use<T>(
|
||||
IProcessor<TInput, T> processor);
|
||||
|
||||
IProcessorChainConfiguration<T, TOutput, TInput, TOutput> Use<T>(
|
||||
Func<IEnumerable<TInput>, IEnumerable<T>> processorFunc);
|
||||
IProcessorChainConfiguration<T, TOutput, TInput, TOutput> Use<T>(
|
||||
Func<IEnumerable<TInput>, IEnumerable<T>> processorFunc);
|
||||
|
||||
IProcessorChainConfiguration<TInput, TOutput> Complete(
|
||||
IProcessor<TInput, TOutput> processor);
|
||||
IProcessorChainConfiguration<TInput, TOutput> Complete(
|
||||
IProcessor<TInput, TOutput> processor);
|
||||
|
||||
IProcessorChainConfiguration<TInput, TOutput> Complete(
|
||||
Func<IEnumerable<TInput>, IEnumerable<TOutput>> processorFunc);
|
||||
}
|
||||
IProcessorChainConfiguration<TInput, TOutput> Complete(
|
||||
Func<IEnumerable<TInput>, IEnumerable<TOutput>> processorFunc);
|
||||
}
|
||||
|
||||
public interface IProcessorChainConfiguration<TInput, TOutput, TInChain, TOutChain>
|
||||
{
|
||||
IProcessorChainConfiguration<T, TOutput, TInChain, TOutChain> Use<T>(
|
||||
IProcessor<TInput, T> processor);
|
||||
public interface IProcessorChainConfiguration<TInput, TOutput, TInChain, TOutChain>
|
||||
{
|
||||
IProcessorChainConfiguration<T, TOutput, TInChain, TOutChain> Use<T>(
|
||||
IProcessor<TInput, T> processor);
|
||||
|
||||
IProcessorChainConfiguration<T, TOutput, TInChain, TOutChain> Use<T>(
|
||||
Func<IEnumerable<TInput>, IEnumerable<T>> processorFunc);
|
||||
IProcessorChainConfiguration<T, TOutput, TInChain, TOutChain> Use<T>(
|
||||
Func<IEnumerable<TInput>, IEnumerable<T>> processorFunc);
|
||||
|
||||
IProcessorChain<TInChain, TOutChain> Complete(
|
||||
IProcessor<TInput, TOutput> processor);
|
||||
IProcessorChain<TInChain, TOutChain> Complete(
|
||||
IProcessor<TInput, TOutput> processor);
|
||||
|
||||
IProcessorChain<TInChain, TOutChain> Complete(
|
||||
Func<IEnumerable<TInput>, IEnumerable<TOutput>> processorFunc);
|
||||
}
|
||||
IProcessorChain<TInChain, TOutChain> Complete(
|
||||
Func<IEnumerable<TInput>, IEnumerable<TOutput>> processorFunc);
|
||||
}
|
||||
\end{CsCode}
|
||||
\caption{Auszug aus Datei "IProcessorChainConfiguration.cs": Schnittstelle zur Konfiguration einer Verarbeitungskette}
|
||||
\label{program:processor_chain_interface}
|
||||
\label{prg:processor_chain_interface}
|
||||
\end{program}
|
||||
|
||||
Ist die Aufbauphase abgeschlossen, kann die Verarbeitungskette schlussendlich gestartet werden.
|
||||
Ist die Aufbauphase abgeschlossen, kann die Verarbeitungskette, konfiguriert in Programm \ref{prg:processor_chain_implementation}, gestartet werden.
|
||||
|
||||
\begin{program}[!ht]
|
||||
\begin{CsCode}[numbers=none]
|
||||
var postProcessor = new ProcessorChainConfiguration<ScanResult, ScanResult>()
|
||||
.Use(new ConfidenceFilter(50))
|
||||
.Use(new ToLowerProcessor())
|
||||
.Use(new DuplicateFilter())
|
||||
.Complete(new RegexFilter(WordRegex));
|
||||
var postProcessor = new ProcessorChainConfiguration<ScanResult, ScanResult>()
|
||||
.Use(new ConfidenceFilter(50))
|
||||
.Use(new ToLowerProcessor())
|
||||
.Use(new DuplicateFilter())
|
||||
.Complete(new RegexFilter(WordRegex));
|
||||
|
||||
// ...
|
||||
// ...
|
||||
|
||||
postProcessor.Process(data);
|
||||
postProcessor.Process(data);
|
||||
\end{CsCode}
|
||||
\caption{Auszug aus Datei "ImageViewModel.cs": Konfiguration und Starten einer Verarbeitungskette}
|
||||
\label{program:processor_chain_implementation}
|
||||
\caption{Auszug aus Datei "ImageViewModel.cs": Konfiguration und Starten einer Verarbeitungskette.}
|
||||
\label{prg:processor_chain_implementation}
|
||||
\end{program}
|
||||
|
||||
Abhängig von den verwendeten Prozessoren können also Eingangsdaten jeglichen Typs, in diesem Fall Bildobjekte der Magick.NET Bibliothek oder Ergebnisdaten des Texterkennungsvorgangs dynamisch verarbeitet werden.
|
||||
@@ -154,66 +156,64 @@ Abhängig von den verwendeten Prozessoren können also Eingangsdaten jeglichen T
|
||||
\subsubsubsection{Bildverarbeitungskette}
|
||||
\label{processor_chain_image}
|
||||
|
||||
Für den grundlegenden Ablauf der Bildverarbeitung und der anschließenden Ergebnisfilterung werden die Erkenntnisse aus \autoref{konzept} mithilfe des in \autoref{components_processorchain} beschriebenen Processing-Frameworks angewandt.
|
||||
Für den Ablauf der Bildverarbeitung und der anschließenden Ergebnisfilterung werden die Erkenntnisse aus \autoref{konzept} mithilfe des in \autoref{components_processorchain} beschriebenen Processing-Frameworks angewandt. Die Resultierende Konfiguration ist in Programm \ref{prg:preprocessor_definition} und Programm \ref{prg:postprocessor_definition} definiert.
|
||||
|
||||
Angefangen mit einem Ausgangsbild, welches über die Softwarebibliothek Magick.NET geladen wurde, beginnt die Bildverarbeitung zunächst mit dem Resampling. Falls der geladene Screenshot die Mindestauflösung von 300dpi unterschreitet, wird es mittels Lanczos2-Verfahren, eine von Magick.NET mitgelieferte Implementierung des Lanczos2-Algorithmus mit leichter Schärfung \mcite{imagemagick}, auf die Mindestauflösung vergrößert. Anschließend wird das Bild normalisiert, in Graustufen umgewandelt und jegliche Transparenz durch einen weißen Hintergrund ersetzt. Danach wird es mittels Schwellwertverfahren binarisiert und rund um das Bild wird ein Rahmen mit einer Dicke von 10px eingefügt. Um Texterkennungsfehler durch falsche Vorder- \bzw Hintergrundfarben auszuschließen, wird das Bild schlussendlich gemeinsam mit einer farblich invertierten Version an das Texterkennungssystem weitergegeben.
|
||||
Angefangen mit einem Ausgangsbild, welches über die Softwarebibliothek Magick.NET geladen wurde, beginnt die Bildverarbeitung zunächst mit dem Resampling. Falls der geladene Screenshot die Mindestauflösung von 300dpi unterschreitet, wird es mittels Lanczos2-Verfahren, eine von Magick.NET mitgelieferte Implementierung des Lanczos2-Algorithmus mit leichter Schärfung \mcite{imagemagick}, auf die Mindestauflösung vergrößert. Anschließend wird das Bild normalisiert, in Graustufen umgewandelt und jegliche Transparenz durch einen weißen Hintergrund ersetzt. Danach wird es mittels Schwellwertverfahren binarisiert. Rund um das Bild wird ein Rahmen mit einer Dicke von 10px eingefügt. Um Texterkennungsfehler durch falsche Vorder- \bzw Hintergrundfarben auszuschließen, wird das Bild gemeinsam mit einer farblich invertierten Version an das Texterkennungssystem weitergegeben.
|
||||
|
||||
\begin{program}[!ht]
|
||||
\begin{CsCode}[numbers=none]
|
||||
var preprocessing = new ProcessorChainConfiguration<MagickImage, MagickImage>()
|
||||
.Use(new CloneImageProcessor())
|
||||
.Use(new ResizeProcessor(FilterType.Lanczos2Sharp, PixelInterpolateMethod.Mesh))
|
||||
.Use(new NormalizeProcessor())
|
||||
.Use(_thresholdProcessor)
|
||||
.Use(new AddBorderProcessor(10))
|
||||
.Use(new BinarizeProcessor())
|
||||
.Use(new NegateCloneProcessor())
|
||||
.Complete(OnPreprocessed);
|
||||
var preprocessing = new ProcessorChainConfiguration<MagickImage, MagickImage>()
|
||||
.Use(new CloneImageProcessor())
|
||||
.Use(new ResizeProcessor(FilterType.Lanczos2Sharp, PixelInterpolateMethod.Mesh))
|
||||
.Use(new NormalizeProcessor())
|
||||
.Use(_thresholdProcessor)
|
||||
.Use(new AddBorderProcessor(10))
|
||||
.Use(new BinarizeProcessor())
|
||||
.Use(new NegateCloneProcessor())
|
||||
.Complete(OnPreprocessed);
|
||||
\end{CsCode}
|
||||
\caption{Auszug aus Datei "EvaluationProcessor.cs": Definition der Preprocessing-Kette}
|
||||
\label{program:program_preprocessor_definition}
|
||||
\caption{Auszug aus Datei "EvaluationProcessor.cs": Definition der Preprocessing-Kette.}
|
||||
\label{prg:preprocessor_definition}
|
||||
\end{program}
|
||||
|
||||
Wurde der übergebene Screenshot vom Texterkennungssystem verarbeitet, müssen nun die Ergebnisse gefiltert werden. Dazu werden zunächst die Metadaten der einzelnen Wörter betrachtet und alle Elemente mit einer Confidence unter einem Schwellenwert von 50\% verworfen. Danach werden die erkannten Texte mittels der \csharp-Funktion ToLower() normalisiert und anschließend auf Duplikate untersucht. Sind alle Duplikate verworfen, werden die Wörter schlussendlich mittels sprachabhängigen Regular Expressions -- in diesem Fall gibt es gemäß den Annahmen in \autoref{annahmen_mehrsprachigkeit} einen Sprachfilter für Englisch und Deutsch -- gefiltert.
|
||||
Wurde der übergebene Screenshot vom Texterkennungssystem verarbeitet, müssen nun die Ergebnisse gefiltert werden. Dazu werden zunächst die Metadaten der einzelnen Wörter betrachtet und alle Elemente mit einer Confidence unter einem Schwellenwert von 50\% verworfen. Danach werden die erkannten Texte mittels der \csharp-Funktion ToLower() normalisiert und anschließend auf Duplikate untersucht. Sind alle Duplikate verworfen, werden die Wörter mittels sprachabhängigen Regular Expressions -- in diesem Fall gibt es gemäß den Annahmen in \autoref{annahmen_mehrsprachigkeit} einen Sprachfilter für Englisch und Deutsch -- gefiltert.
|
||||
|
||||
\begin{program}[!ht]
|
||||
\begin{CsCode}[numbers=none]
|
||||
var postprocessing = new ProcessorChainConfiguration<ScanResult, ScanResult>()
|
||||
.Use(new ConfidenceFilter(50))
|
||||
.Use(new ToLowerProcessor())
|
||||
.Use(new DuplicateFilter())
|
||||
.Complete(new RegexFilter(wordRegex));
|
||||
var postprocessing = new ProcessorChainConfiguration<ScanResult, ScanResult>()
|
||||
.Use(new ConfidenceFilter(50))
|
||||
.Use(new ToLowerProcessor())
|
||||
.Use(new DuplicateFilter())
|
||||
.Complete(new RegexFilter(wordRegex));
|
||||
\end{CsCode}
|
||||
\caption{Auszug aus Datei "EvaluationProcessor.cs": Definition der Postprocessing-Kette}
|
||||
\label{program:program_postprocessor_definition}
|
||||
\caption{Auszug aus Datei "EvaluationProcessor.cs": Definition der Postprocessing-Kette.}
|
||||
\label{prg:postprocessor_definition}
|
||||
\end{program}
|
||||
|
||||
\subsubsection{Lookup}
|
||||
|
||||
Die "Lookup" Bibliothek abstrahiert das Speichern von Schlüssel-Wert-Paaren. Dabei kann flexibel zwischen verschiedenen Speicherimplementierungen gewechselt werden. So ist es beispielsweise möglich, die Werte in einer Listenstruktur im Arbeitsspeicher, in einer Datei oder -- mittels der EntityFramework-Bibliothek, welche von der .NET Foundation entwickelt wird -- in einer Datenbank persistent abzulegen.
|
||||
|
||||
Unabhängig von der Ablagestruktur im Hintergrund können Lookups mittels einer gemeinsamen Schnittstelle manipuliert werden:
|
||||
Die "Lookup" Bibliothek abstrahiert das Speichern von Schlüssel-Wert-Paaren. Dabei kann flexibel zwischen verschiedenen Speicherimplementierungen gewechselt werden. So ist es beispielsweise möglich, die Werte in einer Listenstruktur im Arbeitsspeicher, in einer Datei oder -- mittels der EntityFramework-Bibliothek, welche von der .NET Foundation entwickelt wird -- in einer Datenbank persistent abzulegen. Unabhängig von der Ablagestruktur im Hintergrund können Lookups mittels einer gemeinsamen Schnittstelle, abgebildert in Programm \ref{prg:lookup_interface}, manipuliert werden.
|
||||
|
||||
\begin{program}[!ht]
|
||||
\begin{CsCode}[numbers=none]
|
||||
public interface ILookup<TKey, TValue>
|
||||
: ILookup,
|
||||
IDictionary<TKey, ICollection<TValue>>,
|
||||
IDisposable
|
||||
{
|
||||
ICollection<TValue> Add(TKey key);
|
||||
public interface ILookup<TKey, TValue>
|
||||
: ILookup,
|
||||
IDictionary<TKey, ICollection<TValue>>,
|
||||
IDisposable
|
||||
{
|
||||
ICollection<TValue> Add(TKey key);
|
||||
|
||||
public void Add(TKey key, TValue value);
|
||||
public void Add(TKey key, TValue value);
|
||||
|
||||
public void AddRange(TKey key, IEnumerable<TValue> values);
|
||||
public void AddRange(TKey key, IEnumerable<TValue> values);
|
||||
|
||||
public bool Remove(TKey key, TValue value);
|
||||
public bool Remove(TKey key, TValue value);
|
||||
|
||||
public ICollection<TValue> GetOrAdd(TKey key);
|
||||
}
|
||||
public ICollection<TValue> GetOrAdd(TKey key);
|
||||
}
|
||||
\end{CsCode}
|
||||
\caption{Auszug aus Datei "ILookup.cs": Definition der gemeinsamen Schnittstelle für Lookups}
|
||||
\label{program:program_lookup_interface}
|
||||
\label{prg:lookup_interface}
|
||||
\end{program}
|
||||
|
||||
\subsubsection{OCR}
|
||||
@@ -223,27 +223,27 @@ Die "OCR" Bibliothek enthält elementare Komponenten für die Texterkennung mitt
|
||||
\subsubsection{Automatische Berichterstellung}
|
||||
\label{components_reportgenerator}
|
||||
|
||||
Mithilfe des ReportGenerator-Frameworks wird die automatische Berichterstellung für unterschiedlichste Ausgabeformate abstrahiert. Durch die mitgelieferten Schnittstellendefinitionen ist es möglich, eigene Ausgabeformate zu definieren und den Funktionsumfang des ReportGenerators, wie beispielsweise das Erstellen von Tabellen oder das Anlegen und Überschriften, an die jeweilige Syntax und Dokumentstruktur anzupassen.
|
||||
Mithilfe des ReportGenerator-Frameworks wird die automatische Berichterstellung für unterschiedlichste Ausgabeformate abstrahiert. Durch die mitgelieferten Schnittstellendefinitionen ist es möglich, eigene Ausgabeformate zu definieren. Der gesamte Funktionsumfang des ReportGenerators, beispielsweise das Erstellen von Tabellen oder das Anlegen und Überschriften, kann durch die Implementerung von Interfaces wie Programm \ref{prg:reportgenerator_interface} an die jeweilige Syntax und Dokumentstruktur angepasst werden.
|
||||
|
||||
\begin{program}[!ht]
|
||||
\begin{CsCode}[numbers=none]
|
||||
public interface IDocumentGenerator : IStreamWriter
|
||||
{
|
||||
IDocumentGenerator Append(string? text = default);
|
||||
public interface IDocumentGenerator : IStreamWriter
|
||||
{
|
||||
IDocumentGenerator Append(string? text = default);
|
||||
|
||||
IDocumentGenerator AppendLine(string? text = default);
|
||||
IDocumentGenerator AppendLine(string? text = default);
|
||||
|
||||
IDocumentGenerator AppendParagraph(string? text = default);
|
||||
IDocumentGenerator AppendParagraph(string? text = default);
|
||||
|
||||
IDocumentGenerator AppendHeading(int level, string text);
|
||||
IDocumentGenerator AppendHeading(int level, string text);
|
||||
|
||||
IDocumentGenerator AppendTable(int columns, Action<ITableGenerator> table);
|
||||
IDocumentGenerator AppendTable(int columns, Action<ITableGenerator> table);
|
||||
|
||||
string FormatImage(string path, IBounds? bounds = default);
|
||||
}
|
||||
string FormatImage(string path, IBounds? bounds = default);
|
||||
}
|
||||
\end{CsCode}
|
||||
\caption{Auszug aus Datei "IDocumentGenerator.cs": Hauptschnittstelle für den ReportGenerator}
|
||||
\label{program:program_reportgenerator_interface}
|
||||
\label{prg:reportgenerator_interface}
|
||||
\end{program}
|
||||
|
||||
\subsection{Programmablauf}
|
||||
@@ -252,29 +252,29 @@ Die prototypische Implementierung besteht neben den oben genannten Komponenten a
|
||||
|
||||
\subsubsection{Texterkennung}
|
||||
|
||||
Zu Beginn der Ausführung des Kommandozeilenprogramms wird für jedes zu verarbeitende Bild abhängig von den definierten Schwellenwertverfahren eine Reihe von Prozessoren angelegt. Dazu wurde der statische Teil der Bildverarbeitungskette gemäß \autoref{processor_chain_image} innerhalb der "EvaluationProcessor" Klasse definiert. Lediglich die zu evaluierenden Prozessoren für die jeweiligen Schwellwertverfahren können außerhalb der klasse dynamisch definiert werden. Der EvaluationProcessor legt die erzeugten Ergebnisdaten, bestehend aus den gefundenen Wörtern und zugehörigen Metadaten wie die Confidence, auf Dateiebene ab. Um überprüfen zu können, welches Bild schlussendlich an das Texterkennungssystem übergeben wurde, werden auch die verarbeiteten Bilder nach der Binarisierung gespeichert.
|
||||
Zu Beginn der Ausführung des Kommandozeilenprogramms wird für jedes zu verarbeitende Bild abhängig von den definierten Schwellenwertverfahren eine Reihe von Prozessoren angelegt. Dazu wurde der statische Teil der Bildverarbeitungskette gemäß \autoref{processor_chain_image} innerhalb der "EvaluationProcessor" Klasse definiert, wie in Programm \ref{prg:processor_definition_dynamic} auszugsweise dargestellt. Lediglich die zu evaluierenden Prozessoren für die jeweiligen Schwellwertverfahren können außerhalb der klasse dynamisch definiert werden. Der EvaluationProcessor legt die erzeugten Ergebnisdaten, bestehend aus den gefundenen Wörtern und zugehörigen Metadaten wie die Confidence, auf Dateiebene ab. Um überprüfen zu können, welches Bild schlussendlich an das Texterkennungssystem übergeben wurde, werden auch die verarbeiteten Bilder nach der Binarisierung gespeichert.
|
||||
|
||||
\begin{program}[!ht]
|
||||
\begin{CsCode}[numbers=none]
|
||||
private static IEnumerable<EvaluationProcessor> MakeThresholdVariations()
|
||||
private static IEnumerable<EvaluationProcessor> MakeThresholdVariations()
|
||||
{
|
||||
for (int i = 4; i <= 24; i += 4)
|
||||
{
|
||||
for (int i = 4; i <= 24; i += 4)
|
||||
{
|
||||
yield return new(new ThresholdAdaptiveProcessor(i));
|
||||
}
|
||||
|
||||
for (int i = 20; i <= 80; i += 10)
|
||||
{
|
||||
yield return new(new ThresholdProcessor(i));
|
||||
}
|
||||
|
||||
yield return new(new AutoThresholdProcessor(AutoThresholdMethod.Kapur));
|
||||
yield return new(new AutoThresholdProcessor(AutoThresholdMethod.OTSU));
|
||||
yield return new(new AutoThresholdProcessor(AutoThresholdMethod.Triangle));
|
||||
yield return new(new ThresholdAdaptiveProcessor(i));
|
||||
}
|
||||
|
||||
for (int i = 20; i <= 80; i += 10)
|
||||
{
|
||||
yield return new(new ThresholdProcessor(i));
|
||||
}
|
||||
|
||||
yield return new(new AutoThresholdProcessor(AutoThresholdMethod.Kapur));
|
||||
yield return new(new AutoThresholdProcessor(AutoThresholdMethod.OTSU));
|
||||
yield return new(new AutoThresholdProcessor(AutoThresholdMethod.Triangle));
|
||||
}
|
||||
\end{CsCode}
|
||||
\caption{Auszug aus Datei "Program.cs": Definition der Thresholding Prozessoren}
|
||||
\label{program:program_processor_definition_dynamic}
|
||||
\caption{Auszug aus Datei "Program.cs": Definition der Thresholding Prozessoren.}
|
||||
\label{prg:processor_definition_dynamic}
|
||||
\end{program}
|
||||
|
||||
Ist die Erstellung der Bildbearbeitungsprozessoren abgeschlossen, wird jeder einzelne Prozessor über die Systembibliothek "System.Threading.Tasks" als eigene Ausführungseinheit (\engl{Task}) gestartet. In der Kommandozeile wird anschließend der aktuelle Status jedes Tasks angezeigt. Wurden alle Tasks abgeschlossen, wird das Programm beendet.
|
||||
@@ -283,48 +283,48 @@ Ist die Erstellung der Bildbearbeitungsprozessoren abgeschlossen, wird jeder ein
|
||||
|
||||
Wurden die in den jeweiligen Screenshots erkannten Textdaten abgelegt, werden diese Daten im zweiten Kommandozeilenprogramm "ReportGenerator" nun mit den manuell verschlagworteten Daten verglichen und die Ergebnisse in einen Bericht (\engl{Report}) gespeichert.
|
||||
|
||||
Als zentrale Komponente für den Vergleich spielt die Berechnung der in \autoref{metriken} erklärten Metriken eine wesentliche Rolle. Wie in Programm \ref{program:distance_levenshtein} ersichtlich, wird die Distanz zwischen zwei \csharp-Enumerables, seien es zwei Strings oder zwei Listen, über das Verfahren nach Levenshtein berechnet.
|
||||
Als zentrale Komponente für den Vergleich spielt die Berechnung der in \autoref{metriken} erklärten Metriken eine wesentliche Rolle. Wie in Programm \ref{prg:distance_levenshtein} ersichtlich, wird die Distanz zwischen zwei \csharp-Enumerables, seien es zwei Strings oder zwei Listen, über das Verfahren nach Levenshtein berechnet.
|
||||
|
||||
\begin{program}[!ht]
|
||||
\begin{CsCode}[numbers=none]
|
||||
public static double GetDistance<T>(T reference, T? hypothesis)
|
||||
public static double GetDistance<T>(T reference, T? hypothesis)
|
||||
where T : IEnumerable
|
||||
{
|
||||
var refArr = reference.Cast<object>().ToArray();
|
||||
var hypArr = hypothesis?.Cast<object>().ToArray() ?? Array.Empty<object>();
|
||||
{
|
||||
var refArr = reference.Cast<object>().ToArray();
|
||||
var hypArr = hypothesis?.Cast<object>().ToArray() ?? Array.Empty<object>();
|
||||
|
||||
var distance = new int[refArr.Length + 1, hypArr.Length + 1];
|
||||
var distance = new int[refArr.Length + 1, hypArr.Length + 1];
|
||||
|
||||
for (var x = 0; x <= refArr.Length; x++)
|
||||
{
|
||||
distance[x, 0] = x;
|
||||
}
|
||||
|
||||
for (var y = 0; y <= hypArr.Length; y++)
|
||||
{
|
||||
distance[0, y] = y;
|
||||
}
|
||||
|
||||
for (var x = 0; x < refArr.Length; x++)
|
||||
{
|
||||
for (var y = 0; y < hypArr.Length; y++)
|
||||
{
|
||||
var cost = Equals(refArr[x], hypArr[y]) ? 0 : 1;
|
||||
|
||||
var c1 = distance[x, y] + cost; // Bottom left
|
||||
|
||||
var c2 = distance[x, y + 1] + 1; // Top left
|
||||
var c3 = distance[x + 1, y] + 1; // Bottom right
|
||||
|
||||
distance[x + 1, y + 1] = Min(c1, c2, c3); // Top right
|
||||
}
|
||||
}
|
||||
|
||||
return distance[refArr.Length, hypArr.Length];
|
||||
for (var x = 0; x <= refArr.Length; x++)
|
||||
{
|
||||
distance[x, 0] = x;
|
||||
}
|
||||
|
||||
for (var y = 0; y <= hypArr.Length; y++)
|
||||
{
|
||||
distance[0, y] = y;
|
||||
}
|
||||
|
||||
for (var x = 0; x < refArr.Length; x++)
|
||||
{
|
||||
for (var y = 0; y < hypArr.Length; y++)
|
||||
{
|
||||
var cost = Equals(refArr[x], hypArr[y]) ? 0 : 1;
|
||||
|
||||
var c1 = distance[x, y] + cost; // Bottom left
|
||||
|
||||
var c2 = distance[x, y + 1] + 1; // Top left
|
||||
var c3 = distance[x + 1, y] + 1; // Bottom right
|
||||
|
||||
distance[x + 1, y + 1] = Min(c1, c2, c3); // Top right
|
||||
}
|
||||
}
|
||||
|
||||
return distance[refArr.Length, hypArr.Length];
|
||||
}
|
||||
\end{CsCode}
|
||||
\caption{Auszug aus Datei "Calculator.cs": Berechnung der Levenshtein-Distanz}
|
||||
\label{program:distance_levenshtein}
|
||||
\caption{Auszug aus Datei "Calculator.cs": Berechnung der Levenshtein-Distanz.}
|
||||
\label{prg:distance_levenshtein}
|
||||
\end{program}
|
||||
|
||||
Nach der Ermittlung der jeweiligen Distanzen auf Wort- \bzw Bildbasis werden sie mit den jeweiligen Ursprungsbildern, Prozessoren und den verwendeten Algorithmen in Bezug gesetzt. Die so aufbereiteten Ergebnisse werden anschließend an den ReportGenerator übergeben und in einen Bericht zusammengefasst.
|
||||
Reference in New Issue
Block a user