Overview
Optical Character Recognition (OCR) using Tesseract in Java allows you to extract text from images. Tesseract is a powerful open-source OCR engine maintained by Google.
1. Setup and Dependencies
Maven Dependencies
<!-- pom.xml --> <dependencies> <!-- Tesseract OCR --> <dependency> <groupId>net.sourceforge.tess4j</groupId> <artifactId>tess4j</artifactId> <version>5.8.0</version> </dependency> <!-- Image processing --> <dependency> <groupId>com.twelvemonkeys.imageio</groupId> <artifactId>imageio</artifactId> <version>3.9.4</version> </dependency> <!-- JavaFX for GUI (optional) --> <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-controls</artifactId> <version>17.0.2</version> </dependency> </dependencies>
Tesseract Installation
Windows:
- Download Tesseract from GitHub releases
- Install to
C:\Program Files\Tesseract-OCR\ - Add to PATH or set
TESSDATA_PREFIXenvironment variable
Linux (Ubuntu/Debian):
sudo apt-get update sudo apt-get install tesseract-ocr sudo apt-get install libtesseract-dev
macOS:
brew install tesseract brew install tesseract-lang
2. Basic OCR Implementation
Simple Text Extraction
import net.sourceforge.tess4j.Tesseract; import net.sourceforge.tess4j.TesseractException; import java.io.File; public class BasicOCR { public static String extractTextFromImage(String imagePath) { try { Tesseract tesseract = new Tesseract(); // Set tessdata path (where language data files are located) tesseract.setDatapath("tessdata"); // Set language (default is English) tesseract.setLanguage("eng"); // Perform OCR String result = tesseract.doOCR(new File(imagePath)); return result; } catch (TesseractException e) { System.err.println("OCR Error: " + e.getMessage()); return null; } } public static void main(String[] args) { String imagePath = "sample.png"; String extractedText = extractTextFromImage(imagePath); if (extractedText != null) { System.out.println("Extracted Text:"); System.out.println(extractedText); } else { System.out.println("Failed to extract text from image."); } } } Advanced OCR Configuration
import net.sourceforge.tess4j.Tesseract; import net.sourceforge.tess4j.TesseractException; import net.sourceforge.tess4j.util.ImageHelper; import java.awt.image.BufferedImage; import java.io.File; import javax.imageio.ImageIO; public class AdvancedOCR { private Tesseract tesseract; public AdvancedOCR() { initializeTesseract(); } private void initializeTesseract() { tesseract = new Tesseract(); // Basic configuration tesseract.setDatapath("tessdata"); tesseract.setLanguage("eng"); // Advanced configuration tesseract.setPageSegMode(6); // 6 = Uniform block of text tesseract.setOcrEngineMode(1); // 1 = Neural nets LSTM engine // Performance settings tesseract.setTessVariable("preserve_interword_spaces", "1"); tesseract.setTessVariable("user_defined_dpi", "300"); } public String extractText(String imagePath) throws TesseractException { return tesseract.doOCR(new File(imagePath)); } public String extractTextWithPreprocessing(String imagePath) throws Exception { BufferedImage image = ImageIO.read(new File(imagePath)); // Preprocess image for better OCR results BufferedImage processedImage = preprocessImage(image); return tesseract.doOCR(processedImage); } private BufferedImage preprocessImage(BufferedImage image) { // Convert to grayscale BufferedImage grayscale = ImageHelper.convertImageToGrayscale(image); // Increase contrast BufferedImage highContrast = ImageHelper.convertImageToBinary(grayscale); // Resize image if too small (minimum 300 DPI recommended) if (highContrast.getWidth() < 600) { double scale = 600.0 / highContrast.getWidth(); highContrast = ImageHelper.getScaledInstance( highContrast, (int)(highContrast.getWidth() * scale), (int)(highContrast.getHeight() * scale) ); } return highContrast; } // Method to get confidence scores public void analyzeOCRConfidence(String imagePath) throws TesseractException { File imageFile = new File(imagePath); // Get OCR result with confidence information String result = tesseract.doOCR(imageFile); // Note: Tess4j doesn't directly provide confidence scores in current version // You might need to use different approaches or libraries for confidence analysis System.out.println("OCR Result:"); System.out.println(result); } public static void main(String[] args) { try { AdvancedOCR ocr = new AdvancedOCR(); String imagePath = "document.png"; String text = ocr.extractTextWithPreprocessing(imagePath); System.out.println("Extracted Text:"); System.out.println(text); } catch (Exception e) { e.printStackTrace(); } } } 3. Image Preprocessing for Better OCR
Comprehensive Image Preprocessing
import java.awt.Color; import java.awt.Graphics2D; import java.awt.Image; import java.awt.RenderingHints; import java.awt.image.BufferedImage; import java.awt.image.ConvolveOp; import java.awt.image.Kernel; import java.io.File; import javax.imageio.ImageIO; public class ImagePreprocessor { public static BufferedImage preprocessForOCR(BufferedImage originalImage) { BufferedImage processed = originalImage; // Step 1: Convert to grayscale processed = convertToGrayscale(processed); // Step 2: Noise reduction processed = reduceNoise(processed); // Step 3: Enhance contrast processed = enhanceContrast(processed); // Step 4: Sharpen image processed = sharpenImage(processed); // Step 5: Deskew if needed processed = deskewImage(processed); // Step 6: Resize for better OCR (if image is too small) processed = resizeImage(processed, 2.0); // Double the size return processed; } public static BufferedImage convertToGrayscale(BufferedImage image) { BufferedImage grayscale = new BufferedImage( image.getWidth(), image.getHeight(), BufferedImage.TYPE_BYTE_GRAY); Graphics2D g = grayscale.createGraphics(); g.drawImage(image, 0, 0, null); g.dispose(); return grayscale; } public static BufferedImage reduceNoise(BufferedImage image) { // Apply Gaussian blur for noise reduction float[] matrix = { 1/16f, 2/16f, 1/16f, 2/16f, 4/16f, 2/16f, 1/16f, 2/16f, 1/16f }; Kernel kernel = new Kernel(3, 3, matrix); ConvolveOp op = new ConvolveOp(kernel, ConvolveOp.EDGE_NO_OP, null); return op.filter(image, null); } public static BufferedImage enhanceContrast(BufferedImage image) { // Simple contrast enhancement by stretching histogram int width = image.getWidth(); int height = image.getHeight(); BufferedImage contrasted = new BufferedImage(width, height, image.getType()); int[] histogram = new int[256]; // Build histogram for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { Color color = new Color(image.getRGB(x, y)); int gray = (color.getRed() + color.getGreen() + color.getBlue()) / 3; histogram[gray]++; } } // Find min and max values (ignoring extremes) int min = 0, max = 255; for (int i = 0; i < 256; i++) { if (histogram[i] > width * height * 0.001) { // Ignore extreme darks min = i; break; } } for (int i = 255; i >= 0; i--) { if (histogram[i] > width * height * 0.001) { // Ignore extreme brights max = i; break; } } // Apply contrast stretch for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { Color color = new Color(image.getRGB(x, y)); int gray = (color.getRed() + color.getGreen() + color.getBlue()) / 3; // Stretch contrast int newGray = (gray - min) * 255 / (max - min); newGray = Math.max(0, Math.min(255, newGray)); Color newColor = new Color(newGray, newGray, newGray); contrasted.setRGB(x, y, newColor.getRGB()); } } return contrasted; } public static BufferedImage sharpenImage(BufferedImage image) { // Sharpening kernel float[] matrix = { -1, -1, -1, -1, 9, -1, -1, -1, -1 }; Kernel kernel = new Kernel(3, 3, matrix); ConvolveOp op = new ConvolveOp(kernel, ConvolveOp.EDGE_NO_OP, null); return op.filter(image, null); } public static BufferedImage deskewImage(BufferedImage image) { // Simple deskewing by detecting text angle // This is a simplified version - real deskewing is more complex return image; // Implement proper deskewing as needed } public static BufferedImage resizeImage(BufferedImage image, double scale) { int newWidth = (int) (image.getWidth() * scale); int newHeight = (int) (image.getHeight() * scale); Image scaled = image.getScaledInstance(newWidth, newHeight, Image.SCALE_SMOOTH); BufferedImage resized = new BufferedImage(newWidth, newHeight, image.getType()); Graphics2D g2d = resized.createGraphics(); g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); g2d.drawImage(scaled, 0, 0, null); g2d.dispose(); return resized; } public static void saveImage(BufferedImage image, String outputPath) throws Exception { File output = new File(outputPath); ImageIO.write(image, "png", output); } public static void main(String[] args) { try { // Load original image BufferedImage original = ImageIO.read(new File("input.jpg")); // Preprocess image BufferedImage processed = preprocessForOCR(original); // Save processed image saveImage(processed, "processed.png"); System.out.println("Image preprocessing completed."); } catch (Exception e) { e.printStackTrace(); } } } 4. Multi-language OCR
Supporting Multiple Languages
import net.sourceforge.tess4j.Tesseract; import net.sourceforge.tess4j.TesseractException; import java.io.File; import java.util.HashMap; import java.util.Map; public class MultiLanguageOCR { private Tesseract tesseract; private Map<String, String> languageMap; public MultiLanguageOCR() { initializeTesseract(); initializeLanguages(); } private void initializeTesseract() { tesseract = new Tesseract(); tesseract.setDatapath("tessdata"); tesseract.setPageSegMode(6); } private void initializeLanguages() { languageMap = new HashMap<>(); languageMap.put("english", "eng"); languageMap.put("spanish", "spa"); languageMap.put("french", "fra"); languageMap.put("german", "deu"); languageMap.put("chinese", "chi_sim"); languageMap.put("japanese", "jpn"); languageMap.put("korean", "kor"); languageMap.put("arabic", "ara"); languageMap.put("russian", "rus"); } public String detectLanguage(String imagePath) throws TesseractException { // Simple language detection by trying multiple languages String bestLanguage = "eng"; int bestScore = 0; for (String langCode : languageMap.values()) { try { tesseract.setLanguage(langCode); String result = tesseract.doOCR(new File(imagePath)); // Simple scoring based on valid characters and word count int score = calculateOCRScore(result); if (score > bestScore) { bestScore = score; bestLanguage = langCode; } } catch (TesseractException e) { // Language data might not be available continue; } } return bestLanguage; } private int calculateOCRScore(String text) { if (text == null || text.trim().isEmpty()) { return 0; } // Count words (simple whitespace splitting) String[] words = text.trim().split("\\s+"); int wordCount = words.length; // Count valid characters (alphanumeric and common punctuation) int validChars = 0; for (char c : text.toCharArray()) { if (Character.isLetterOrDigit(c) || ".,!?;:-'\"()[]{} ".indexOf(c) >= 0) { validChars++; } } return wordCount * 10 + validChars; } public OCRResult extractText(String imagePath, String language) throws TesseractException { String langCode = languageMap.getOrDefault(language.toLowerCase(), "eng"); tesseract.setLanguage(langCode); long startTime = System.currentTimeMillis(); String text = tesseract.doOCR(new File(imagePath)); long endTime = System.currentTimeMillis(); return new OCRResult(text, langCode, endTime - startTime); } public OCRResult extractTextAutoLanguage(String imagePath) throws TesseractException { String detectedLanguage = detectLanguage(imagePath); return extractText(imagePath, getLanguageName(detectedLanguage)); } private String getLanguageName(String langCode) { for (Map.Entry<String, String> entry : languageMap.entrySet()) { if (entry.getValue().equals(langCode)) { return entry.getKey(); } } return "english"; } public static class OCRResult { private final String text; private final String language; private final long processingTime; public OCRResult(String text, String language, long processingTime) { this.text = text; this.language = language; this.processingTime = processingTime; } // Getters public String getText() { return text; } public String getLanguage() { return language; } public long getProcessingTime() { return processingTime; } @Override public String toString() { return String.format("Language: %s\nProcessing Time: %dms\nText:\n%s", language, processingTime, text); } } public static void main(String[] args) { try { MultiLanguageOCR ocr = new MultiLanguageOCR(); String imagePath = "multi_language_document.png"; // Auto-detect language OCRResult result = ocr.extractTextAutoLanguage(imagePath); System.out.println("Auto-detected Result:"); System.out.println(result); // Extract with specific language OCRResult frenchResult = ocr.extractText(imagePath, "french"); System.out.println("\nFrench-specific Result:"); System.out.println(frenchResult); } catch (Exception e) { e.printStackTrace(); } } } 5. Batch Processing and PDF OCR
Batch Image Processing
import net.sourceforge.tess4j.Tesseract; import net.sourceforge.tess4j.TesseractException; import java.io.*; import java.nio.file.*; import java.util.ArrayList; import java.util.List; import java.util.concurrent.*; public class BatchOCRProcessor { private Tesseract tesseract; private ExecutorService executorService; public BatchOCRProcessor() { initializeTesseract(); executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); } private void initializeTesseract() { tesseract = new Tesseract(); tesseract.setDatapath("tessdata"); tesseract.setLanguage("eng"); tesseract.setPageSegMode(6); } public List<OCRJobResult> processDirectory(String directoryPath) throws Exception { List<Path> imageFiles = findImageFiles(directoryPath); List<Future<OCRJobResult>> futures = new ArrayList<>(); for (Path imageFile : imageFiles) { Callable<OCRJobResult> task = () -> processSingleFile(imageFile); futures.add(executorService.submit(task)); } List<OCRJobResult> results = new ArrayList<>(); for (Future<OCRJobResult> future : futures) { try { results.add(future.get()); } catch (Exception e) { System.err.println("Error processing file: " + e.getMessage()); } } return results; } private List<Path> findImageFiles(String directoryPath) throws IOException { List<Path> imageFiles = new ArrayList<>(); Files.walk(Paths.get(directoryPath)) .filter(path -> { String fileName = path.getFileName().toString().toLowerCase(); return fileName.endsWith(".jpg") || fileName.endsWith(".jpeg") || fileName.endsWith(".png") || fileName.endsWith(".tiff") || fileName.endsWith(".bmp") || fileName.endsWith(".gif"); }) .forEach(imageFiles::add); return imageFiles; } private OCRJobResult processSingleFile(Path imageFile) { try { long startTime = System.currentTimeMillis(); String text = tesseract.doOCR(imageFile.toFile()); long endTime = System.currentTimeMillis(); return new OCRJobResult( imageFile.getFileName().toString(), text, endTime - startTime, true, null ); } catch (TesseractException e) { return new OCRJobResult( imageFile.getFileName().toString(), "", 0, false, e.getMessage() ); } } public void exportResults(List<OCRJobResult> results, String outputDir) throws IOException { Path outputPath = Paths.get(outputDir); Files.createDirectories(outputPath); for (OCRJobResult result : results) { if (result.isSuccess()) { String outputFileName = result.getFileName().replaceAll("\\.[^.]*$", "") + ".txt"; Path outputFile = outputPath.resolve(outputFileName); Files.write(outputFile, result.getText().getBytes()); } } // Create summary report createSummaryReport(results, outputPath.resolve("ocr_summary.csv")); } private void createSummaryReport(List<OCRJobResult> results, Path summaryPath) throws IOException { StringBuilder csv = new StringBuilder(); csv.append("File Name,Success,Processing Time (ms),Text Length,Error\n"); for (OCRJobResult result : results) { csv.append(String.format("\"%s\",%s,%d,%d,\"%s\"\n", result.getFileName(), result.isSuccess(), result.getProcessingTime(), result.getText().length(), result.getError() != null ? result.getError() : "" )); } Files.write(summaryPath, csv.toString().getBytes()); } public void shutdown() { executorService.shutdown(); try { if (!executorService.awaitTermination(60, TimeUnit.SECONDS)) { executorService.shutdownNow(); } } catch (InterruptedException e) { executorService.shutdownNow(); Thread.currentThread().interrupt(); } } public static class OCRJobResult { private final String fileName; private final String text; private final long processingTime; private final boolean success; private final String error; public OCRJobResult(String fileName, String text, long processingTime, boolean success, String error) { this.fileName = fileName; this.text = text; this.processingTime = processingTime; this.success = success; this.error = error; } // Getters public String getFileName() { return fileName; } public String getText() { return text; } public long getProcessingTime() { return processingTime; } public boolean isSuccess() { return success; } public String getError() { return error; } } public static void main(String[] args) { BatchOCRProcessor processor = null; try { processor = new BatchOCRProcessor(); String inputDirectory = "images/"; String outputDirectory = "ocr_results/"; System.out.println("Starting batch OCR processing..."); List<OCRJobResult> results = processor.processDirectory(inputDirectory); System.out.println("Exporting results..."); processor.exportResults(results, outputDirectory); // Print summary long totalFiles = results.size(); long successfulFiles = results.stream().filter(OCRJobResult::isSuccess).count(); long totalProcessingTime = results.stream().mapToLong(OCRJobResult::getProcessingTime).sum(); System.out.printf("Batch Processing Complete:%n"); System.out.printf("Total Files: %d%n", totalFiles); System.out.printf("Successful: %d%n", successfulFiles); System.out.printf("Failed: %d%n", totalFiles - successfulFiles); System.out.printf("Total Processing Time: %dms%n", totalProcessingTime); System.out.printf("Average Time per File: %.2fms%n", (double) totalProcessingTime / totalFiles); } catch (Exception e) { e.printStackTrace(); } finally { if (processor != null) { processor.shutdown(); } } } } 6. JavaFX OCR Application
GUI OCR Application
import javafx.application.Application; import javafx.geometry.Insets; import javafx.scene.Scene; import javafx.scene.control.*; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.layout.*; import javafx.stage.FileChooser; import javafx.stage.Stage; import net.sourceforge.tess4j.Tesseract; import net.sourceforge.tess4j.TesseractException; import java.io.File; public class OCRGUIApplication extends Application { private Tesseract tesseract; private TextArea resultTextArea; private ImageView imageView; private Label statusLabel; private File currentImageFile; @Override public void start(Stage primaryStage) { initializeTesseract(); setupUI(primaryStage); } private void initializeTesseract() { tesseract = new Tesseract(); tesseract.setDatapath("tessdata"); tesseract.setLanguage("eng"); } private void setupUI(Stage stage) { // Create UI components BorderPane root = new BorderPane(); root.setPadding(new Insets(10)); // Top menu MenuBar menuBar = createMenuBar(stage); // Image display imageView = new ImageView(); imageView.setFitWidth(400); imageView.setFitHeight(300); imageView.setPreserveRatio(true); VBox imageBox = new VBox(10, new Label("Selected Image:"), imageView); imageBox.setPadding(new Insets(10)); // Result area resultTextArea = new TextArea(); resultTextArea.setPromptText("Extracted text will appear here..."); resultTextArea.setWrapText(true); VBox resultBox = new VBox(10, new Label("Extracted Text:"), resultTextArea); resultBox.setPadding(new Insets(10)); // Control buttons Button extractButton = new Button("Extract Text"); extractButton.setOnAction(e -> extractText()); Button clearButton = new Button("Clear"); clearButton.setOnAction(e -> clearResults()); HBox buttonBox = new HBox(10, extractButton, clearButton); buttonBox.setPadding(new Insets(10)); // Status bar statusLabel = new Label("Ready"); statusLabel.setPadding(new Insets(5)); // Layout HBox contentBox = new HBox(20, imageBox, resultBox); VBox mainBox = new VBox(10, menuBar, contentBox, buttonBox, statusLabel); root.setCenter(mainBox); Scene scene = new Scene(root, 900, 600); stage.setTitle("OCR Text Extraction Tool"); stage.setScene(scene); stage.show(); } private MenuBar createMenuBar(Stage stage) { MenuBar menuBar = new MenuBar(); // File menu Menu fileMenu = new Menu("File"); MenuItem openItem = new MenuItem("Open Image"); openItem.setOnAction(e -> openImage(stage)); MenuItem saveItem = new MenuItem("Save Text"); saveItem.setOnAction(e -> saveText()); MenuItem exitItem = new MenuItem("Exit"); exitItem.setOnAction(e -> stage.close()); fileMenu.getItems().addAll(openItem, new SeparatorMenuItem(), saveItem, exitItem); // Language menu Menu languageMenu = new Menu("Language"); RadioMenuItem englishItem = new RadioMenuItem("English"); englishItem.setSelected(true); englishItem.setOnAction(e -> tesseract.setLanguage("eng")); RadioMenuItem spanishItem = new RadioMenuItem("Spanish"); spanishItem.setOnAction(e -> tesseract.setLanguage("spa")); RadioMenuItem frenchItem = new RadioMenuItem("French"); frenchItem.setOnAction(e -> tesseract.setLanguage("fra")); ToggleGroup languageGroup = new ToggleGroup(); englishItem.setToggleGroup(languageGroup); spanishItem.setToggleGroup(languageGroup); frenchItem.setToggleGroup(languageGroup); languageMenu.getItems().addAll(englishItem, spanishItem, frenchItem); menuBar.getMenus().addAll(fileMenu, languageMenu); return menuBar; } private void openImage(Stage stage) { FileChooser fileChooser = new FileChooser(); fileChooser.setTitle("Open Image File"); fileChooser.getExtensionFilters().addAll( new FileChooser.ExtensionFilter("Image Files", "*.png", "*.jpg", "*.jpeg", "*.gif", "*.bmp"), new FileChooser.ExtensionFilter("All Files", "*.*") ); File file = fileChooser.showOpenDialog(stage); if (file != null) { currentImageFile = file; displayImage(file); statusLabel.setText("Image loaded: " + file.getName()); } } private void displayImage(File file) { try { Image image = new Image(file.toURI().toString()); imageView.setImage(image); } catch (Exception e) { showAlert("Error", "Could not load image: " + e.getMessage()); } } private void extractText() { if (currentImageFile == null) { showAlert("Warning", "Please select an image first."); return; } try { statusLabel.setText("Extracting text..."); // Run OCR in background thread new Thread(() -> { try { String extractedText = tesseract.doOCR(currentImageFile); // Update UI on JavaFX thread javafx.application.Platform.runLater(() -> { resultTextArea.setText(extractedText); statusLabel.setText("Text extraction completed successfully."); }); } catch (TesseractException e) { javafx.application.Platform.runLater(() -> { showAlert("OCR Error", "Failed to extract text: " + e.getMessage()); statusLabel.setText("Text extraction failed."); }); } }).start(); } catch (Exception e) { showAlert("Error", "Unexpected error: " + e.getMessage()); } } private void clearResults() { resultTextArea.clear(); imageView.setImage(null); currentImageFile = null; statusLabel.setText("Ready"); } private void saveText() { if (resultTextArea.getText().isEmpty()) { showAlert("Warning", "No text to save."); return; } FileChooser fileChooser = new FileChooser(); fileChooser.setTitle("Save Text"); fileChooser.getExtensionFilters().add( new FileChooser.ExtensionFilter("Text Files", "*.txt") ); File file = fileChooser.showSaveDialog(resultTextArea.getScene().getWindow()); if (file != null) { try { java.nio.file.Files.write(file.toPath(), resultTextArea.getText().getBytes()); statusLabel.setText("Text saved to: " + file.getName()); } catch (Exception e) { showAlert("Error", "Could not save file: " + e.getMessage()); } } } private void showAlert(String title, String message) { Alert alert = new Alert(Alert.AlertType.INFORMATION); alert.setTitle(title); alert.setHeaderText(null); alert.setContentText(message); alert.showAndWait(); } @Override public void stop() { // Cleanup resources if needed } public static void main(String[] args) { launch(args); } } Key Features Covered:
- Basic OCR Setup - Tesseract configuration and simple text extraction
- Image Preprocessing - Techniques to improve OCR accuracy
- Multi-language Support - Working with different languages
- Batch Processing - Processing multiple files efficiently
- GUI Application - User-friendly OCR tool with JavaFX
- Performance Optimization - Threading and efficient processing
This comprehensive OCR implementation provides everything needed to integrate text recognition capabilities into Java applications, from basic usage to advanced features like multi-language support and batch processing.