You click the button, VBA hands the PDF to Python, and the OCR run comes back with fragments: half a dimension here, a stray word there. The failure is structural, not a bug in your loop. A scanned drawing sheet has no objects to read — the lines, arcs and lettering you see are pixels inside one embedded image. This how-to builds the pipeline that does read scanned drawings: test each page for real text objects, rasterize the scanned ones at a dpi chosen from the smallest lettering, binarize and OCR region by region, then hand the fields back to Excel.
Why the objects will not read
A scanner produces one raster image per page, and the PDF wrapper around it adds no geometry. There is no line entity, no text object, nothing for a program to enumerate — so "read the objects" can only mean "recognize text from pixels". Whole-page OCR over a busy drawing returns fragments; one widely shared practitioner report puts usable output near 30% of the sheet's text.
The exception matters. A PDF exported straight from CAD is vector-born: its lettering is real text objects, and extraction returns them exactly, with no OCR step. Test before you rasterize — extract text and count characters per page. Fewer than about 20 characters on a drawing page means the page is a scan; rasterize it. More means extract directly and skip OCR.
If a born-digital PDF still returns garbled strings, the defect sits upstream of the recognizer; the trace in PDF OCR text corruption: trace the source before preprocessing starts at font encoding rather than OCR.
Choose the dpi from the smallest lettering
OCR accuracy rests on character height in pixels. Recognizers want at least 20 px of letter height and settle near 30 px — the floor the minimum font size rule for OCR works from. Raster dpi sets it:
Worked example, standard 2.5 mm dimension lettering at 300 dpi: pixel size = 25.4 / 300 = 0.0847 mm, so the letters stand 2.5 / 0.0847 = 29.5 px tall — inside the comfortable band. The same arithmetic gives a decision for every sheet:
| dpi | Pixel size | 2.5 mm text | A1 page | Page memory, 8-bit |
|---|---|---|---|---|
| 200 | 0.127 mm | 19.7 px | 31 MP | 31 MB |
| 300 | 0.0847 mm | 29.5 px | 70 MP | 70 MB |
| 400 | 0.0635 mm | 39.4 px | 124 MP | 124 MB |
| 600 | 0.0423 mm | 59.1 px | 279 MP | 279 MB |
Decisions: 300 dpi for 2.5 mm lettering and larger; 400 dpi when small notes run down to 1.3 mm (1.3 / 0.0635 = 20.5 px); 600 dpi for 1.0 mm lettering (1.0 / 0.0423 = 23.6 px). Lettering below 1 mm is past rescanning — re-print that region.
Page size follows the same math. A1 is 841 × 594 mm: 841 / 25.4 × 300 = 9933 px and 594 / 25.4 × 300 = 7016 px, so 9933 × 7016 = 69.7 MP — call it 70 MB of 8-bit grayscale per page. An ANSI D sheet, 22 × 34 in, lands at 6600 × 10200 px = 67.3 MP. Store the raster as PNG; line art compresses well, typically 10–30 MB per A1 page.
The pipeline in seven steps
- Test the PDF. Extract text per page and count characters; fewer than about 20 marks a scanned page.
-
Rasterize the scanned pages in grayscale at the chosen dpi:
convert_from_path(pdf_in, dpi=300, grayscale=True)returns one image per page. -
Binarize per region. Crop the title block and callout areas first, then threshold each crop on its own — Otsu's method in Lab color explains the choice, and
cv2.adaptiveThresholdis the safer default on uneven scans. -
OCR per region. Whole callout areas in sparse-text mode (
--psm 11), single title-block cells in single-line mode (--psm 7). -
Parse the fields. Split the title block into its grid cells, then match a drawing-number pattern such as
A-[0-9]{4}; patterns survive OCR noise that free text does not. - Write a CSV, one row per page, page index attached.
- Archive all three artifacts per sheet: original PDF, rasterized PNG, fields CSV. The PNG is the audit trail when a field reads wrong later.
Call Python from Excel, and wait
Pass the script two arguments — the drawing path and the output CSV path — and let Excel import the CSV afterwards. Passing a column number through the command line couples the two programs for no gain; the CSV is the interface.
Dim pdfPath As String, outCsv As String, cmd As String
pdfPath = "C:\drawings\A-1042_revB.pdf" ' chosen in your file dialog
outCsv = "C:\drawings\out\A-1042_revB.csv"
cmd = "python ""C:\ocr\pdf_to_excel.py"" """ & pdfPath & """ """ & outCsv & """"
CreateObject("WScript.Shell").Run cmd, 0, True ' True = wait for exit
If Len(Dir(outCsv)) > 0 Then
Workbooks.Open outCsv ' import the fields into the sheet
End If
The True third argument is what makes the handshake work: without it Excel keeps running while Python starts, and Dir() can find last week's CSV. Delete the previous CSV before the call if you want the file test to prove this run finished.
The Python script, end to end
A short script runs the whole first pass; region crops are pixel coordinates measured once per drawing format.
import sys, csv, numpy as np
from pdf2image import convert_from_path
import cv2, pytesseract
pdf_in, csv_out = sys.argv[1], sys.argv[2]
rows = []
for i, page in enumerate(convert_from_path(pdf_in, dpi=300, grayscale=True), 1):
img = np.array(page) # 8-bit grayscale raster
binary = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_MEAN_C,
cv2.THRESH_BINARY, 31, 15)
text = pytesseract.image_to_string(binary, config="--psm 11")
cell = pytesseract.image_to_string(binary[5500:5900, 7400:9700],
config="--psm 7") # one title-block cell
rows.append([i, " ".join(text.split()), " ".join(cell.split())])
with open(csv_out, "w", newline="", encoding="utf-8") as f:
csv.writer(f).writerows(rows)
The crop binary[5500:5900, 7400:9700] measures 400 × 2300 px — about 34 × 195 mm at 300 dpi, one title-block row. Sheets that share a drawing frame share crop coordinates, so you measure once per format.
Accept or flag each sheet
Judge the output field by field, not page by page. Three checks catch nearly every bad sheet:
- Field count. If the title block has 12 labelled cells and the run returns 9, flag the sheet; a missing field is a binarization failure, not noise.
- Pattern match. Drawing numbers, revisions and dates have shapes. Anything outside the pattern is wrong even when it looks plausible.
- Spot audit. Read five dimension callouts against the sheet on every new drawing format, then keep a 1-in-20 sampling check in place.
Confidence scores from the OCR engine sort flagged fields for review, cheapest first. Where fields keep coming back wrong, the region is almost always at fault, not the recognizer — diagnosing missing characters in OCR walks the filtering side. Structured capture from scanned pages is the same discipline the forms world uses; the walkthrough on automating scanned PDF data extraction shows the field-level checks from that side.
Five mistakes that eat weekends
- One threshold for the whole sheet. A drawing mixes dense ink and faint stamps; threshold per region.
- Rasterizing vector pages. If extraction returns text, OCR only adds errors — test per page, not per file.
- Skipping deskew. Straighten each page on the title-block border first; a degree or two of tilt starts splitting vertical strokes.
- Rotated landscape pages. Scans are often stored 90° from the drawing's reading orientation; rotate before OCR and store the rotation with the page index.
- Blueprint polarity. White-line prints read badly as-is; invert so lettering sits dark on light, then run the same pipeline.
Where the batch should run
A few sheets a week runs on any office desktop. Dedicated hardware earns its slot in two cases that look identical on the spec sheet: hundreds of pages per night, and a machine parked in the production area that nobody reboots. That is the vision controllers class — fanless, several LAN ports for office and cell networks, storage for the archive, no fans to clog.
Three models cover the range from first batch box to AI-OCR host:
| Specification | MV-VC2140-128G40-1T | MV-VC5525-256G70-NN | MV-VC4712-256G10-2TH |
|---|---|---|---|
| CPU | Intel® Processor N97 | Intel® Core i5-12400 | Intel® Core Ultra 7 processor 255H |
| Memory | 8 GB | 32 GB | 2 × DDR5 6400 MHz non-ECC SO-DIMM memory slots; Up to 48 GB per slot, Up to 96 GB with two slots |
| Storage | 128 GB SSD + 1 TB HDD | 256 GB SSD | 1 × 7-pin SATA3.0, 1 × M.2 SATA 2280, 1 × M.2 NVMe 2280; 1 × 2.5"/3.5" drive bay |
| GPU | Intel® UHD Graphics | Intel® UHD Graphics 730 | Intel® Arc 140T GPU |
| Operating system | Unactivated 64-bit Windows 10 LTSC 21H2 | Unactivated Windows 10 | Windows 11 64-bit (not activated) |
| Encryption method | Not support | – | MV-VC47**-**6-**: VM6200pro encryption supported; MV-VC47**-**0-**: encryption not supported |
| Video output | HDMI interface × 1, VGA interface × 1; Supports independent display outputs at the same time; Max. resolution 1920 × 1080 @ 60 Hz | HDMI interface × 1, VGA interface × 1; Supports independent display output at the same time; Max. resolution 1920 × 1080 @ 60 Hz | Independent dual display supported; 1× HDMI port, Up to 1920 × 1080 @ 60 Hz; 1× VGA port, Up to 1920 × 1080 @ 60 Hz |
| Digital I/O | Opto-isolated input × 8; Opto-isolated output × 8 | Opto-isolated input × 8; Opto-isolated output × 12 ( supports NPN/PNP switching), digital-isolated low-side driver output × 8; Non-isolated bi-directional differential I/O × 4 | 18 digital isolated inputs supported; 18 isolated digital outputs |
| Light interface | Light interface with 24 VDC constant voltage × 4; (Brightness is adjustable. Max. current 3 A for single light interface, and total max. power consumption 80 W. Supports light mode switching between strobe and solid via external control) | 24 VDC constant voltage × 8; (Max. power consumption of single channel is 72 W, total max. power consumption 190 W) | SMR-03V-BC female; 6-channel 24 V constant-voltage light drive, Max. power per channel: 72 W, Max. total power: 110 W |
| Network interface | GigE interface × 4 | GigE interface × 7, including interface for PoE × 4 | 4× Gigabit Ethernet ports; PoE power supported, 15 W per port, 25 W total across 4 ports |
| USB Interface | USB 2.0 × 2; USB 3.0 × 2 | USB 2.0 × 4, USB 3.0 × 4, built-in USB 2.0 × 1 | 4× USB 2.0 ports; 2× USB 3.0 ports; 1 × internal USB2.0 port |
| Serial Port | RS-232 × 1, supports switching to RS-485 or RS-422 | RS-232 × 2 by default, supports switching to RS-485 and RS-422 | 2 × configurable RS-232/RS-422/RS-485 |
| Price | $1,679.00 | $4,479.00 · quoted | $6,049.00 · quoted |
All 3 share: Certification CE, KC. Values as printed in each product's data sheet; open a part for its drawing and full specifications.
Classic OCR at 300 dpi holds one 70 MB page in flight and fits the 8 GB tier: the MV-VC2140-128G40-1T pairs 8 GB of memory with a 1 TB HDD for the sheet archive. AI OCR models want more headroom, which is the 32 GB tier — the MV-VC5525-256G70-NN carries 32 GB of memory and a 256 GB SSD, with PoE camera ports and 8 built-in light channels for a future capture station. For an archive of tens of thousands of sheets with the models still resident, the MV-VC4712-256G10-2TH steps up to a 2 TB HDD and PoE camera ports.
- LAN ports
- 5+ LAN ports
- USB 3 ports
- 3-4 USB 3 ports
- PoE
- PoE camera ports
- Light outputs
- 8 built-in channels
When the sheet outgrows the scanner
Sheets too large or too fragile for the scanner get photographed instead, and the pixel budget carries over unchanged: required pixels = region width / required pixel size. A 300 × 150 mm title block at 0.1 mm per pixel needs 3000 × 1500 px = 4.5 MP, and 2.5 mm lettering stands at 2.5 / 0.1 = 25 px — inside the reliable band. The workable pattern is one shot per region: camera on a copy stand, title block captured on its own, each dense callout area entering the pipeline as its own page.
A smart camera covers a title-block shot in one frame: the LS-SMC-0017 records 8.8 MP with a 1-inch sensor, a C-mount and a global shutter.
- Interface
- USB 2.0
- Resolution
- 6-10 MP
- Sensor format
- 1"
- Shutter
- Global
Lens choice decides sharpness at stand distance; the working-distance math is in document imaging lens selection for sharp full-page capture. When a region needs more pixels than one frame delivers, the camera finder filters by resolution and mount.
The decisions, and the next step
Decisions in one line each: extract before you rasterize; 300 dpi for 2.5 mm lettering, 400 dpi at 1.3 mm, 600 dpi at 1.0 mm; binarize and OCR per region; accept per field, never per page. Size the batch machine from the three controllers above, add a camera when sheets outgrow the scanner, and lay out the whole station in the system configurator. When a drawing format resists the pipeline, the contact page reaches the applications team.
Questions
How do I tell whether a drawing PDF is vector or scanned?
Open the PDF and try to select text with the cursor, or run a text-extraction pass in Python: fewer than about 20 characters on a page marks it as scanned. Vector PDFs exported from CAD return their text objects directly and should never be rasterized for OCR; mixed files need the test applied per page, keeping the page index with each result.
Why does OCR read the diameter symbol as 0 or 8?
Dimension symbols such as Ø, R and ± sit outside most engines' default character sets, so they collapse into the digit they most resemble. Fix it in post-processing: whitelist the digits plus the symbols a region can contain, and keep a replace map for recurring confusions. Measure the fix per field — one corrected map entry clears every sheet that shares the drawing format.
How much storage does a rasterized drawing archive need?
An A1 sheet at 300 dpi is about 70 MB as raw 8-bit grayscale; the same page saved as PNG line art typically lands between 10 and 30 MB. At a 20 MB average, 1 TB holds roughly 50,000 sheets, so a 2 TB archive disk carries a five-digit library with the originals alongside.
Can I scan blueprints and dark photocopies the same way?
Yes, after an inversion step. White-line prints and dark photocopies break thresholding assumptions; invert the page so lettering is dark on light, then run the same 300 dpi, per-region pipeline. Test one title block first: if strokes come back gappy after inversion, re-rasterize at 400 dpi and re-test before touching the threshold.
What is the smallest letter height OCR can read on a drawing?
Twenty pixels of character height is the floor, with about 30 px comfortable. Pixel size at 300 dpi is 0.0847 mm, so the floor lands at 20 × 0.0847 = 1.7 mm of lettering; standard 2.5 mm dimension text gives 29.5 px. Lettering at 1.0 mm needs 600 dpi to clear 20 px (1.0 / 0.0423 = 23.6 px); below that, re-print the region rather than re-scan.

