Performing OCR on Japanese PDFs Using Tesseract and Pytesseract
Extracting Japanese text from a PDF with Tesseract OCR v4 and pytesseract, including the normalization step needed to clean up the output.
Tesseract OCR v4 and its Python wrapper pytesseract can extract Japanese text from PDFs.
The source text is Run, Melos! by Osamu Dazai, a work that is now in the public domain.
Requirements
Install the following Python libraries:
Tesseract OCR itself is also required. This example installs it in a Docker image; see the official repository for other installation options.
Implementation
Writing the Python Script
import refrom datetime import datetime
import pdf2imageimport pytesseract
def to_images(pdf_path: str, first_page: int = None, last_page: int = None) -> list: """ Convert a PDF to a PNG image.
Args: pdf_path (str): PDF path first_page (int): First page starting 1 to be converted last_page (int): Last page to be converted
Returns: list: List of image data """
print(f'Convert a PDF ({pdf_path}) to a png...') images = pdf2image.convert_from_path( pdf_path=pdf_path, fmt='png', first_page=first_page, last_page=last_page, ) print(f'A total of converted png images is {len(images)}.') return images
def to_string(image) -> str: """ OCR an image data.
Args: image: Image data
Returns: str: OCR processed characters """
print(f'Extract characters from an image...') return pytesseract.image_to_string(image, lang='jpn')
def normalize(target: str) -> str: """ Normalize result text.
Applying the following: - Remove new line. - Remove spaces between Japanese characters.
Args: target (str): Target text to be normalized
Returns: str: Normalized text """
result = re.sub('\n', '', target) result = re.sub('([あ-んア-ン一-鿐])\s+((?=[あ-んア-ン一-鿐]))', r'\1\2', result) return result
def save(result: str) -> str: """ Save the result text in a text file.
Args: result (str): Result text
Returns: str: Text file path """
path = 'result.txt' with open(path, 'w') as f: f.write(result) return path
def main() -> None: start = datetime.now() result = ''
images = to_images('run-melos.pdf', 1, 2) for image in images: result += to_string(image) result = normalize(result) path = save(result)
end = datetime.now() duration = end.timestamp() - start.timestamp()
print('----------------------------------------') print(f'Start: {start}') print(f'End: {end}') print(f'Duration: {int(duration)} seconds') print(f'Result file path: {path}') print('----------------------------------------')
if __name__ == '__main__': main()Creating the Dockerfile
The run-melos.pdf on line 6 can be downloaded here.
FROM python:3.10
WORKDIR /usr/src/appCOPY app.py ./COPY requirements.txt ./COPY run-melos.pdf ./RUN apt update && apt install -y poppler-utils tesseract-ocr tesseract-ocr-jpn \ && pip install -r requirements.txt
CMD ["python", "app.py"]# CMD ["/bin/sh", "-c", "while :; do sleep 10; done"]Testing
The script uses pdf2image to convert the PDF pages into PNG image data, extracts text with Tesseract OCR through pytesseract, and saves the result to a text file:
NAME=pytesseract-sampledocker build -t $NAME .docker run --name $NAME $NAME
# You can see OCR processing result in `result.txt`.docker cp $NAME:/usr/src/app/result.txt ./less result.txt
# Clean updocker container rm $NAMEdocker image rm $NAMEResult
The result can be downloaded from:
Conclusion
Processing a public-domain Japanese PDF with pdf2image and Tesseract in a Docker container produced readable text that closely matched the original.
The lang='jpn' argument to pytesseract.image_to_string activates Tesseract’s Japanese trained-data file. The regular expression in normalize is also important: it removes whitespace that Tesseract tends to insert between Japanese characters, which would otherwise make accurate output look fragmented.
Compare the result directly with the original text of Run, Melos! because a quick review can miss substitutions between visually similar kanji.
Because this PDF contains clean, digitally rendered text rather than scanned pages, the accuracy is close to a best-case result. Lower-resolution or scanned material would likely require image preprocessing before OCR.
Related posts
Monitoring OPC UA Variable Nodes with Python
Subscribing to OPC UA variable node changes with opcua-asyncio so the client reacts to pushed updates instead of polling.
Installing Python Packages Without Internet Access
Download Python packages on a connected machine, transfer them, and install them in an environment without internet access.
Running Proxy.py as a Lightweight HTTP Proxy on EC2
Running Proxy.py on an EC2 instance and reaching it safely through an SSH tunnel, since the proxy has no authentication of its own.
Dependency Injection for Maintainable, Testable Code
Refactor a tightly coupled TypeScript class by injecting its salary calculator, system clock, and Amazon SES mailer.
Spying on Mock Object Properties in Jasmine
Working around Jasmine's "already been spied upon" error when spying on a mocked object's property with Object.getOwnPropertyDescriptor.
