{"id":36,"date":"2026-03-01T14:10:10","date_gmt":"2026-03-01T13:10:10","guid":{"rendered":"http:\/\/serwer402453.lh.pl\/autoinstalator\/serwer402453.lh.pl\/wordpress143401\/?p=36"},"modified":"2026-06-01T16:22:06","modified_gmt":"2026-06-01T14:22:06","slug":"ocr-document-intelligence","status":"publish","type":"post","link":"https:\/\/chmielinski-konrad.info\/index.php\/2026\/03\/01\/ocr-document-intelligence\/","title":{"rendered":"OCR &amp; Document Intelligence"},"content":{"rendered":"\n<!DOCTYPE html>\n<html lang=\"pl\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title>OCR &#038; Document Intelligence &mdash; Konrad Chmielinski<\/title>\n    <link rel=\"preconnect\" href=\"https:\/\/fonts.googleapis.com\">\n    <link rel=\"preconnect\" href=\"https:\/\/fonts.gstatic.com\" crossorigin>\n    <link rel=\"stylesheet\" href=\"https:\/\/fonts.googleapis.com\/css2?family=JetBrains+Mono:wght@400;500;700&#038;family=Sora:wght@300;400;600;700&#038;display=swap\" media=\"print\" onload=\"this.media='all'\">\n    <noscript>\n        <link rel=\"stylesheet\" href=\"https:\/\/fonts.googleapis.com\/css2?family=JetBrains+Mono:wght@400;500;700&#038;family=Sora:wght@300;400;600;700&#038;display=swap\">\n    <\/noscript>\n    <style>\n    :root {\n        --accent: #185FA5;\n        --accent-dim: #0C447C;\n        --accent-bg: #E6F1FB;\n        --mono: 'JetBrains Mono', monospace;\n        --sans: 'Sora', sans-serif;\n    }\n    * { box-sizing: border-box; margin: 0; padding: 0; }\n    body { background: #fff; color: #111; -webkit-overflow-scrolling: touch; overflow-x: hidden; }\n    .blog-root { font-family: var(--sans); color: #111; max-width: 100%; margin: 0 auto; padding: 2rem 1rem 4rem; line-height: 1.7; }\n    .eyebrow { font-family: var(--mono); font-size: 11px; letter-spacing: .12em; color: var(--accent-dim); text-transform: uppercase; margin-bottom: .75rem; display: flex; align-items: center; gap: 8px; }\n    .eyebrow::before { content: ''; display: inline-block; width: 24px; height: 1.5px; background: var(--accent); }\n    h1.blog-title { font-family: var(--sans); font-size: 2.2rem; font-weight: 700; line-height: 1.2; margin-bottom: .5rem; color: #111; }\n    .subtitle { font-size: 1.05rem; color: #555; font-weight: 300; margin-bottom: 1.75rem; }\n    .meta-row { display: flex; align-items: center; gap: 1rem; padding: .75rem 0; border-top: .5px solid #ddd; border-bottom: .5px solid #ddd; margin-bottom: 2rem; flex-wrap: wrap; }\n    .meta-chip { font-family: var(--mono); font-size: 11px; background: #f4f4f2; border: .5px solid #ddd; border-radius: 4px; padding: 3px 8px; color: #555; }\n    .meta-chip.blue { background: var(--accent-bg); color: var(--accent-dim); border-color: #B5D4F4; }\n    .tech-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 10px; margin: 1.5rem 0 2rem; }\n    .tech-card { background: #fff; border: .5px solid #ddd; border-radius: 12px; padding: .875rem 1rem; position: relative; }\n    .tech-card .tc-label { font-family: var(--mono); font-size: 10px; color: #777; letter-spacing: .08em; text-transform: uppercase; margin-bottom: 4px; }\n    .tech-card .tc-name { font-size: 14px; font-weight: 600; color: #111; margin-bottom: 4px; }\n    .tech-card .tc-note { font-size: 12px; color: #555; line-height: 1.4; }\n    .tech-card .tc-dot { position: absolute; top: 12px; right: 12px; width: 7px; height: 7px; border-radius: 50%; background: var(--accent); }\n    .section-header { display: flex; align-items: center; gap: 10px; margin: 2.25rem 0 1rem; }\n    .section-header h2 { font-family: var(--sans); font-size: 1.15rem; font-weight: 600; color: #111; }\n    .sh-line { flex: 1; height: .5px; background: #ddd; }\n    .sh-badge { font-family: var(--mono); font-size: 10px; background: var(--accent-bg); color: var(--accent-dim); border-radius: 4px; padding: 2px 8px; white-space: nowrap; border: .5px solid #B5D4F4; }\n    p.body { font-size: 15px; color: #111; margin-bottom: 1rem; }\n    .pipeline-visual { background: #f7f7f5; border: .5px solid #ddd; border-radius: 12px; padding: 1.25rem 1.5rem; margin: 1.5rem 0; overflow-x: auto; -webkit-overflow-scrolling: touch; touch-action: pan-x pan-y; }\n    .pipeline-row { display: flex; align-items: center; gap: 8px; flex-wrap: nowrap; }\n    .pipe-step { background: #fff; border: .5px solid #ddd; border-radius: 6px; padding: 6px 12px; font-family: var(--mono); font-size: 11px; color: #111; white-space: nowrap; font-weight: 500; }\n    .pipe-step.hl { background: var(--accent-bg); border-color: #B5D4F4; color: var(--accent-dim); }\n    .pipe-arrow { color: #999; font-size: 12px; flex-shrink: 0; }\n    .code-block { background: #f7f7f5; border: .5px solid #ddd; border-left: 2.5px solid var(--accent); border-radius: 0 8px 8px 0; padding: 1rem 1.25rem; margin: 1rem 0 1.5rem; font-family: var(--mono); font-size: 12px; color: #555; line-height: 1.65; overflow-x: auto; }\n    .code-block .kw { color: #185FA5; font-weight: 500; }\n    .code-block .str { color: #BA7517; }\n    .code-block .cmt { color: #999; }\n    .tag-cloud { display: flex; flex-wrap: wrap; gap: 6px; margin: 1rem 0 1.5rem; }\n    .tag { font-family: var(--mono); font-size: 11px; padding: 4px 10px; border-radius: 100px; border: .5px solid #ccc; color: #555; background: #fff; }\n    .tag.primary { background: var(--accent-bg); border-color: #B5D4F4; color: var(--accent-dim); font-weight: 500; }\n    .callout { background: #f7f7f5; border-left: 2px solid var(--accent); border-radius: 0 8px 8px 0; padding: 1rem 1.25rem; margin: 1.5rem 0; font-size: 14px; color: #555; line-height: 1.65; }\n    .callout strong { color: #111; font-weight: 600; }\n    .stat-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 10px; margin: 1.5rem 0; }\n    .stat-card { background: #f7f7f5; border-radius: 8px; padding: 1rem; text-align: center; }\n    .stat-card .stat-num { font-size: 2rem; font-weight: 700; color: var(--accent-dim); font-family: var(--mono); }\n    .stat-card .stat-label { font-size: 12px; color: #555; margin-top: 4px; }\n    .employer-row { display: grid; grid-template-columns: 1fr; gap: 10px; margin: 1rem 0; }\n    .employer-card { display: flex; gap: 12px; align-items: flex-start; background: #fff; border: .5px solid #ddd; border-radius: 12px; padding: .875rem 1rem; }\n    .emp-logo { width: 36px; height: 36px; border-radius: 8px; background: var(--accent-bg); display: flex; align-items: center; justify-content: center; font-family: var(--mono); font-size: 10px; font-weight: 700; color: var(--accent-dim); flex-shrink: 0; letter-spacing: -.02em; text-align: center; line-height: 1.3; }\n    .emp-body .emp-name { font-size: 13px; font-weight: 600; color: #111; }\n    .emp-body .emp-desc { font-size: 12px; color: #555; margin-top: 3px; line-height: 1.5; }\n    .compare-table { width: 100%; border-collapse: collapse; font-size: 13px; margin: 1.5rem 0; }\n    .compare-table th { font-family: var(--mono); font-size: 10px; letter-spacing: .08em; text-transform: uppercase; color: #777; font-weight: 500; padding: 8px 12px; border-bottom: .5px solid #ddd; text-align: left; }\n    .compare-table td { padding: 8px 12px; border-bottom: .5px solid #f0f0f0; color: #555; vertical-align: top; }\n    .compare-table td:first-child { font-weight: 600; color: #111; white-space: nowrap; }\n    .compare-table tr:last-child td { border-bottom: none; }\n    .footer-note { font-family: var(--mono); font-size: 11px; color: #777; border-top: .5px solid #ddd; margin-top: 3rem; padding-top: 1rem; display: flex; justify-content: space-between; flex-wrap: wrap; gap: 4px; }\n    .footer-note a { color: #555; text-decoration: none; border-bottom: .5px solid #bbb; transition: color .15s, border-color .15s; }\n    .footer-note a:hover { color: var(--accent-dim); border-color: var(--accent); }\n    <\/style>\n<\/head>\n<body>\n    <div class=\"blog-root\">\n        <div class=\"eyebrow\">Engineering Blog &middot; Konrad Chmielinski<\/div>\n        <p class=\"subtitle\">Turning unstructured documents into structured, machine-readable data &mdash; at enterprise scale.<\/p>\n\n        <div class=\"meta-row\">\n            <span class=\"meta-chip blue\">Document AI<\/span>\n            <span class=\"meta-chip\">ABBYY &middot; Azure &middot; OCR<\/span>\n            <span class=\"meta-chip\">NL \/ FR \/ BE \/ DE &amp; more<\/span>\n            <span class=\"meta-chip\">Katowice, PL &middot; Remote OK<\/span>\n        <\/div>\n\n        <p class=\"body\">Most enterprise data does not live in neat database tables. It lives in scanned PDFs, faxed contracts, handwritten forms, identity documents, and chemical research reports &mdash; formats that no SQL query can touch. Document Intelligence is the engineering discipline of bridging that gap: extracting, validating, and routing information from unstructured sources into systems that can actually use it.<\/p>\n        <p class=\"body\">I have built and maintained production document intelligence pipelines across two employers, four Western European markets, and multiple document types. This post covers the full stack &mdash; tools, architectures, accuracy patterns, and real project outcomes.<\/p>\n\n        <div class=\"stat-row\">\n            <div class=\"stat-card\"><div class=\"stat-num\">70%<\/div><div class=\"stat-label\">end-to-end accuracy on multi-market identity &amp; PoA docs (ING)<\/div><\/div>\n            <div class=\"stat-card\"><div class=\"stat-num\">4+<\/div><div class=\"stat-label\">Western European markets covered (NL \/ FR \/ BE \/ DE &amp; more)<\/div><\/div>\n            <div class=\"stat-card\"><div class=\"stat-num\">2<\/div><div class=\"stat-label\">employers with production OCR deployments<\/div><\/div>\n            <div class=\"stat-card\"><div class=\"stat-num\">3+<\/div><div class=\"stat-label\">distinct OCR engines used in production<\/div><\/div>\n        <\/div>\n\n        <div class=\"section-header\"><h2>The core challenge<\/h2><div class=\"sh-line\"><\/div><div class=\"sh-badge\">why it is hard<\/div><\/div>\n        <p class=\"body\">Document intelligence is deceptively difficult. A scanned PDF of a Dutch power of attorney document looks nothing like a Belgian one &mdash; different layout, different fonts, different field positions, different languages, and potentially a handwritten signature that must be detected as present or absent. Any pipeline that handles one handles none of the others unless explicitly engineered to do so.<\/p>\n        <p class=\"body\">The core pipeline pattern is always the same, but every layer requires deliberate engineering decisions:<\/p>\n\n        <div class=\"pipeline-visual\">\n            <div class=\"pipeline-row\">\n                <div class=\"pipe-step hl\">Ingest<\/div><div class=\"pipe-arrow\">&rarr;<\/div>\n                <div class=\"pipe-step\">Classify<\/div><div class=\"pipe-arrow\">&rarr;<\/div>\n                <div class=\"pipe-step hl\">OCR \/ Extract<\/div><div class=\"pipe-arrow\">&rarr;<\/div>\n                <div class=\"pipe-step\">Validate<\/div><div class=\"pipe-arrow\">&rarr;<\/div>\n                <div class=\"pipe-step hl\">Enrich<\/div><div class=\"pipe-arrow\">&rarr;<\/div>\n                <div class=\"pipe-step\">Route<\/div><div class=\"pipe-arrow\">&rarr;<\/div>\n                <div class=\"pipe-step hl\">Store<\/div>\n            <\/div>\n        <\/div>\n\n        <div class=\"section-header\"><h2>OCR engines I have used in production<\/h2><div class=\"sh-line\"><\/div><div class=\"sh-badge\">tool comparison<\/div><\/div>\n        <div class=\"tech-grid\">\n            <div class=\"tech-card\"><div class=\"tc-dot\"><\/div><div class=\"tc-label\">On-premise &middot; batch<\/div><div class=\"tc-name\">ABBYY FlexiCapture<\/div><div class=\"tc-note\">Trained document classifiers, zonal OCR, rule-based field extraction. Best for high-volume structured forms.<\/div><\/div>\n            <div class=\"tech-card\"><div class=\"tc-dot\"><\/div><div class=\"tc-label\">Cloud &middot; ML-based<\/div><div class=\"tc-name\">ABBYY Vantage<\/div><div class=\"tc-note\">ML skill training for semi-structured docs. Handles layout variance better than FlexiCapture.<\/div><\/div>\n            <div class=\"tech-card\"><div class=\"tc-dot\"><\/div><div class=\"tc-label\">Cloud &middot; Azure<\/div><div class=\"tc-name\">Azure Document Intelligence<\/div><div class=\"tc-note\">Form Recognizer + custom models. Best Azure-native integration, REST API, continuous model retraining.<\/div><\/div>\n            <div class=\"tech-card\"><div class=\"tc-dot\"><\/div><div class=\"tc-label\">Cloud &middot; Azure<\/div><div class=\"tc-name\">Azure Computer Vision OCR<\/div><div class=\"tc-note\">Raw text extraction from scanned pages. Used as a fallback or pre-processing step before structured extraction.<\/div><\/div>\n        <\/div>\n\n        <table class=\"compare-table\">\n            <thead><tr><th>Engine<\/th><th>Best for<\/th><th>Layout variance<\/th><th>Deployment<\/th><\/tr><\/thead>\n            <tbody>\n                <tr><td>ABBYY FlexiCapture<\/td><td>High-volume structured forms<\/td><td>Low tolerance<\/td><td>On-premise \/ server<\/td><\/tr>\n                <tr><td>ABBYY Vantage<\/td><td>Semi-structured, variable layouts<\/td><td>Medium tolerance<\/td><td>Cloud \/ hybrid<\/td><\/tr>\n                <tr><td>Azure Document Intelligence<\/td><td>Azure-integrated pipelines, APIs<\/td><td>High tolerance<\/td><td>Azure cloud<\/td><\/tr>\n                <tr><td>Azure Computer Vision OCR<\/td><td>Raw text extraction, preprocessing<\/td><td>Very high<\/td><td>Azure cloud<\/td><\/tr>\n            <\/tbody>\n        <\/table>\n\n        <div class=\"section-header\"><h2>Document classification<\/h2><div class=\"sh-line\"><\/div><div class=\"sh-badge\">before extraction<\/div><\/div>\n        <p class=\"body\">Before any field can be extracted, the pipeline must know what type of document it is looking at. At ING, I built a classifier that distinguished between power of attorney documents and identity documents across four markets &mdash; each with different layouts and languages. The classifier routed documents to the correct extraction model before any OCR ran.<\/p>\n        <div class=\"tag-cloud\">\n            <span class=\"tag primary\">Document classifiers<\/span><span class=\"tag primary\">Scikit-Learn<\/span><span class=\"tag primary\">ABBYY FlexiCapture trained models<\/span>\n            <span class=\"tag\">Azure Custom Document Models<\/span><span class=\"tag\">Layout heuristics<\/span><span class=\"tag\">Language detection<\/span><span class=\"tag\">Multi-format routing<\/span><span class=\"tag\">PDF metadata parsing<\/span>\n        <\/div>\n\n        <div class=\"section-header\"><h2>Field extraction patterns<\/h2><div class=\"sh-line\"><\/div><div class=\"sh-badge\">the core logic<\/div><\/div>\n        <p class=\"body\">Extraction strategy depends entirely on document structure. I use three patterns depending on the document type:<\/p>\n        <div class=\"tech-grid\">\n            <div class=\"tech-card\"><div class=\"tc-dot\"><\/div><div class=\"tc-label\">Pattern 1<\/div><div class=\"tc-name\">Zonal extraction<\/div><div class=\"tc-note\">Fixed-position fields. Define bounding boxes per document type. Works perfectly for standardised forms.<\/div><\/div>\n            <div class=\"tech-card\"><div class=\"tc-dot\"><\/div><div class=\"tc-label\">Pattern 2<\/div><div class=\"tc-name\">Key-value extraction<\/div><div class=\"tc-note\">Label-value pairs. OCR finds &#8220;Name:&#8221; and extracts the adjacent value. Tolerant of layout shifts.<\/div><\/div>\n            <div class=\"tech-card\"><div class=\"tc-dot\"><\/div><div class=\"tc-label\">Pattern 3<\/div><div class=\"tc-name\">ML model extraction<\/div><div class=\"tc-note\">Azure Document Intelligence custom models trained on labelled examples. Best for high-variance layouts.<\/div><\/div>\n            <div class=\"tech-card\"><div class=\"tc-dot\"><\/div><div class=\"tc-label\">Pattern 4<\/div><div class=\"tc-name\">LLM-assisted extraction<\/div><div class=\"tc-note\">GPT-4o over OCR text output for complex or ambiguous documents where rules break down.<\/div><\/div>\n        <\/div>\n\n        <div class=\"code-block\">\n            <span class=\"cmt\"># Azure Document Intelligence extraction pattern<\/span><br>\n            <span class=\"kw\">from<\/span> azure.ai.formrecognizer <span class=\"kw\">import<\/span> DocumentAnalysisClient<br>\n            <span class=\"kw\">from<\/span> azure.core.credentials <span class=\"kw\">import<\/span> AzureKeyCredential<br><br>\n            client = DocumentAnalysisClient(<br>\n            &nbsp;&nbsp;endpoint=<span class=\"str\">&#8220;https:\/\/your-resource.cognitiveservices.azure.com&#8221;<\/span>,<br>\n            &nbsp;&nbsp;credential=AzureKeyCredential(api_key)<br>\n            )<br>\n            poller = client.begin_analyze_document(<span class=\"str\">&#8220;prebuilt-document&#8221;<\/span>, document)<br>\n            result = poller.result()<br><br>\n            <span class=\"kw\">for<\/span> kv_pair <span class=\"kw\">in<\/span> result.key_value_pairs:<br>\n            &nbsp;&nbsp;<span class=\"kw\">if<\/span> kv_pair.key <span class=\"kw\">and<\/span> kv_pair.value:<br>\n            &nbsp;&nbsp;&nbsp;&nbsp;fields[kv_pair.key.content] = kv_pair.value.content\n        <\/div>\n\n        <div class=\"section-header\"><h2>Signature detection<\/h2><div class=\"sh-line\"><\/div><div class=\"sh-badge\">beyond text<\/div><\/div>\n        <p class=\"body\">One of the more unusual requirements at ING was detecting the <em>presence<\/em> of a handwritten signature on power of attorney documents. A document without a signature is legally invalid and must be rejected before it enters the processing queue. I built a computer vision step using OpenCV and Pillow that analysed pixel density in defined signature zones &mdash; a simple but effective binary classifier that ran ahead of the full OCR pipeline.<\/p>\n        <div class=\"tag-cloud\">\n            <span class=\"tag primary\">OpenCV<\/span><span class=\"tag primary\">Pillow<\/span>\n            <span class=\"tag\">Pixel density analysis<\/span><span class=\"tag\">Bounding box ROI<\/span><span class=\"tag\">Binary classification<\/span><span class=\"tag\">Pre-processing gate<\/span><span class=\"tag\">Numpy array ops<\/span>\n        <\/div>\n\n        <div class=\"section-header\"><h2>Validation &amp; post-processing<\/h2><div class=\"sh-line\"><\/div><div class=\"sh-badge\">trust but verify<\/div><\/div>\n        <p class=\"body\">Raw OCR output is never trusted directly. Every extracted field goes through a validation layer &mdash; format checks (dates, ID numbers, postcodes), cross-field consistency checks, and confidence score thresholds. Low-confidence extractions are flagged for human review rather than silently passed downstream.<\/p>\n        <div class=\"tag-cloud\">\n            <span class=\"tag primary\">Pydantic validation models<\/span><span class=\"tag primary\">Confidence score thresholds<\/span>\n            <span class=\"tag\">Regex field validation<\/span><span class=\"tag\">Cross-field consistency<\/span><span class=\"tag\">Human-in-the-loop routing<\/span><span class=\"tag\">Exception logging<\/span><span class=\"tag\">Pandas post-processing<\/span>\n        <\/div>\n        <div class=\"callout\"><strong>Key principle:<\/strong> a pipeline that silently passes bad data is worse than one that flags it for review. Every production pipeline I build has an explicit exception lane &mdash; documents that fall below confidence threshold go to a human queue, not into the database.<\/div>\n\n        <div class=\"section-header\"><h2>Production deployments<\/h2><div class=\"sh-line\"><\/div><div class=\"sh-badge\">employer context<\/div><\/div>\n        <div class=\"employer-row\">\n\n            <div class=\"employer-card\">\n                <div class=\"emp-logo\">ING<br>1<\/div>\n                <div class=\"emp-body\">\n                    <div class=\"emp-name\">ING Hubs Poland (GKYC) &mdash; Power of Attorney Pipeline, 2021&ndash;2023<\/div>\n                    <div class=\"emp-desc\">\n                        Built with <strong>ABBYY FlexiCapture &amp; ABBYY Vantage<\/strong>. The pipeline processed Power of Attorney documents (fr. <em>Pouvoir de gestion<\/em>) across NL\/FR\/BE\/DE markets. The first step was country and document-type recognition &mdash; each market produced different layouts and languages requiring separate classification models.<br><br>\n                        Core extraction goal: detect all instances of <strong>account holders<\/strong> and <strong>proxies<\/strong> (representatives granted authority) within a single document, including cases with multiple parties. For document types that required it, a pre-processing gate detected the <strong>presence of a handwritten signature<\/strong> using pixel density analysis (OpenCV + Pillow) &mdash; documents without a valid signature were rejected before entering the OCR queue. Achieved 70% end-to-end accuracy across all markets.\n                    <\/div>\n                <\/div>\n            <\/div>\n\n            <div class=\"employer-card\">\n                <div class=\"emp-logo\">ING<br>2<\/div>\n                <div class=\"emp-body\">\n                    <div class=\"emp-name\">ING Hubs Poland (GKYC) &mdash; Identity Document Scan &amp; Code Extraction, 2021&ndash;2023<\/div>\n                    <div class=\"emp-desc\">\n                        Built with <strong>ABBYY FlexiCapture &amp; ABBYY Vantage<\/strong>. Pipeline for extracting structured data from scanned identity documents (national IDs, passports, residence permits) across <strong>20+ countries<\/strong> in Europe and beyond. The primary technical challenge was reading and parsing the <strong>machine-readable codes and data fields on document reverses<\/strong> &mdash; including MRZ (Machine Readable Zone) lines, barcodes, and country-specific coded fields that vary significantly in format and position between issuing states.<br><br>\n                        Each country required its own extraction model due to differences in document layout, field encoding, and script. The pipeline covered a broad geographic scope well beyond the Western European core, handling document variants from Central and Eastern Europe and other regions.\n                    <\/div>\n                <\/div>\n            <\/div>\n\n            <div class=\"employer-card\">\n                <div class=\"emp-logo\">AM<\/div>\n                <div class=\"emp-body\">\n                    <div class=\"emp-name\">ArcelorMittal BCoE Poland &mdash; Chemical Research Report Extraction, 2023&ndash;present<\/div>\n                    <div class=\"emp-desc\">\n                        Took over and maintained an existing <strong>Azure Document Intelligence<\/strong> pipeline for extracting data from chemical laboratory research reports. The core challenge was the <strong>high layout variance<\/strong> &mdash; reports originated from laboratories across the world, each producing documents in different formats, structures, and conventions, with layouts changing frequently over time.<br><br>\n                        My role was to design and maintain <strong>predefined extraction rules<\/strong> for each report type as new variants appeared, mapping extracted fields into <strong>standardised columns in a central database<\/strong>. This required continuous rule authoring, regression testing when layouts shifted, and ensuring that data from heterogeneous sources landed correctly in a unified schema for downstream reporting and analytics.\n                    <\/div>\n                <\/div>\n            <\/div>\n\n        <\/div>\n\n        <div class=\"section-header\"><h2>Extended tech stack<\/h2><div class=\"sh-line\"><\/div><div class=\"sh-badge\">full picture<\/div><\/div>\n        <div class=\"tag-cloud\">\n            <span class=\"tag primary\">ABBYY FlexiCapture<\/span><span class=\"tag primary\">ABBYY Vantage<\/span><span class=\"tag primary\">Azure Document Intelligence<\/span>\n            <span class=\"tag primary\">Azure Form Recognizer<\/span><span class=\"tag primary\">Azure Computer Vision OCR<\/span>\n            <span class=\"tag\">OpenCV<\/span><span class=\"tag\">Pillow<\/span><span class=\"tag\">Pytesseract (evaluation)<\/span>\n            <span class=\"tag\">Pydantic<\/span><span class=\"tag\">Pandas<\/span><span class=\"tag\">Scikit-Learn<\/span>\n            <span class=\"tag\">Python requests<\/span><span class=\"tag\">Azure Blob Storage<\/span><span class=\"tag\">SQL \/ MongoDB<\/span>\n            <span class=\"tag\">GPT-4o (LLM-assisted extraction)<\/span><span class=\"tag\">LangChain<\/span><span class=\"tag\">Jenkins CI\/CD<\/span><span class=\"tag\">Git<\/span>\n        <\/div>\n\n        <div class=\"section-header\"><h2>Python image processing &amp; OCR libraries<\/h2><div class=\"sh-line\"><\/div><div class=\"sh-badge\">python ecosystem<\/div><\/div>\n        <p class=\"body\">All image pre-processing in my pipelines is written in Python. Pillow handles format conversion, rotation correction, and colour normalisation before OCR engines receive the image. The four most widely used open-source Python OCR libraries are:<\/p>\n        <div class=\"tech-grid\">\n            <div class=\"tech-card\">\n                <div class=\"tc-dot\"><\/div>\n                <div class=\"tc-label\">Wrapper &middot; Tesseract<\/div>\n                <div class=\"tc-name\">Pytesseract<\/div>\n                <div class=\"tc-note\">Python binding for Google&#8217;s Tesseract engine. Industry-standard open-source OCR, supports 100+ languages. Ideal for Latin-script documents with clean scan quality.<\/div>\n            <\/div>\n            <div class=\"tech-card\">\n                <div class=\"tc-dot\"><\/div>\n                <div class=\"tc-label\">Deep learning &middot; GPU<\/div>\n                <div class=\"tc-name\">EasyOCR<\/div>\n                <div class=\"tc-note\">PyTorch-based, supports 80+ languages out of the box. Excellent on complex layouts and mixed-language documents. No external engine required.<\/div>\n            <\/div>\n            <div class=\"tech-card\">\n                <div class=\"tc-dot\"><\/div>\n                <div class=\"tc-label\">Wrapper &middot; multi-engine<\/div>\n                <div class=\"tc-name\">docTR<\/div>\n                <div class=\"tc-note\">Mindee&#8217;s TensorFlow\/PyTorch OCR library focused on document understanding. Clean API, strong on structured business documents and receipts.<\/div>\n            <\/div>\n        <\/div>\n        <div class=\"tag-cloud\">\n            <span class=\"tag primary\">Pillow (PIL)<\/span>\n            <span class=\"tag primary\">Pytesseract<\/span>\n            <span class=\"tag primary\">EasyOCR<\/span>\n            <span class=\"tag primary\">PaddleOCR<\/span>\n            <span class=\"tag primary\">docTR<\/span>\n            <span class=\"tag\">Python 3<\/span>\n            <span class=\"tag\">NumPy<\/span>\n            <span class=\"tag\">OpenCV<\/span>\n            <span class=\"tag\">PyTorch<\/span>\n        <\/div>\n\n        <div class=\"footer-note\">\n            <span><a href=\"mailto:konrad.chmielinski@outlook.com\">konrad.chmielinski@outlook.com<\/a><\/span>\n            <span><a href=\"https:\/\/www.linkedin.com\/in\/konrad-chmieli%C5%84ski-ba568320a\/\" target=\"_blank\" rel=\"noopener noreferrer\">linkedin.com\/in\/konrad-chmielinski<\/a><\/span>\n            <span>Open to remote &middot; Full-time \/ B2B<\/span>\n        <\/div>\n    <\/div>\n<\/body>\n<\/html>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n","protected":false},"excerpt":{"rendered":"<p>OCR &#038; Document Intelligence &mdash; Konrad Chmielinski Engineering Blog &middot; Konrad Chmielinski Turning unstructured documents into structured, machine-readable data &mdash; at enterprise scale. Document AI ABBYY &middot; Azure &middot; OCR NL \/ FR \/ BE \/ DE &amp; more Katowice, PL &middot; Remote OK Most enterprise data does not live in neat database tables. It lives in scanned PDFs, faxed [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":64,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[3],"tags":[],"class_list":["post-36","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-skills"],"views":42,"_links":{"self":[{"href":"https:\/\/chmielinski-konrad.info\/index.php\/wp-json\/wp\/v2\/posts\/36","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/chmielinski-konrad.info\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/chmielinski-konrad.info\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/chmielinski-konrad.info\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/chmielinski-konrad.info\/index.php\/wp-json\/wp\/v2\/comments?post=36"}],"version-history":[{"count":10,"href":"https:\/\/chmielinski-konrad.info\/index.php\/wp-json\/wp\/v2\/posts\/36\/revisions"}],"predecessor-version":[{"id":565,"href":"https:\/\/chmielinski-konrad.info\/index.php\/wp-json\/wp\/v2\/posts\/36\/revisions\/565"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/chmielinski-konrad.info\/index.php\/wp-json\/wp\/v2\/media\/64"}],"wp:attachment":[{"href":"https:\/\/chmielinski-konrad.info\/index.php\/wp-json\/wp\/v2\/media?parent=36"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/chmielinski-konrad.info\/index.php\/wp-json\/wp\/v2\/categories?post=36"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/chmielinski-konrad.info\/index.php\/wp-json\/wp\/v2\/tags?post=36"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}