This tutorial shows how to multi-thread the OCR engine for parallel processing using the LEADTOOLS SDK in a C/C++ Windows API application.
| Overview | |
|---|---|
| Summary | This tutorial covers how to multi-thread the OCR Engine using the LEAD Auto Recognize Manager in a Windows C DLL Application. |
| Completion Time | 45 minutes |
| Visual Studio Project | Download tutorial project (19 KB) |
| Platform | Windows C DLL Application |
| IDE | Visual Studio 2017, 2019, 2022 |
| Development License | Download LEADTOOLS |
| Try it in another language |
|
Before working on the Multi-thread OCR with the Auto Recognize Manager - Windows C DLL tutorial, get familiar with the basic steps of creating a project by reviewing the Add References and Set a License tutorial.
Start with a copy of the 64-bit Windows API project created in the Add References and Set a License tutorial. If the project is not available, create it by following the steps in that tutorial.
To utilize LEADTOOLS OCR functionality, additional header and DLL files are required. Open the pre-compiled headers file (either pch.h or stdafx.h, depending on the version of Visual Studio used) and add the following lines:
#include "C:\LEADTOOLS23\Include\ltocr.h"#pragma comment (lib, "C:\\LEADTOOLS23\\Lib\\CDLL\\x64\\Ltocr_x.lib") // OCR support
Note: For a complete list of which DLL files are required for your OCR application, refer to Files to be Included with your Application.
The License unlocks the features needed for the project. It must be set before any toolkit function is called. For details, including tutorials for different platforms, refer to Setting a Runtime License.
There are two types of runtime licenses:
Note: Adding LEADTOOLS references and setting a license are covered in more detail in the Add References and Set a License tutorial.
Now that the LEADTOOLS references have been added and the license has been set, coding can begin.
The steps below are for Visual Studio 2019; they could be different for other versions of Visual Studio.
Go to the Solution Explorer and double-click the resources file (.rc).
Expand the menu in the resources tree and double-click the menu resource to open it in the designer interface.
In the empty item below the Exit item, click and type Multithread &OCR. Drag the new item above Exit. This should cause the item's ID to become ID_FILE_MULTITHREADOCR.
Go to the main CPP file of the project and navigate to the WndProc function of the main window. Under the switch (wmId) statement, that is below the WM_COMMAND case, add a new case:
switch (wmId){case ID_FILE_MULTITHREADOCR:{TCHAR szFolderIn[1024] = TEXT(""); // Input files location// Choose input files folderif (SUCCESS != GetSourceFolder(hWnd, szFolderIn, ARRAYSIZE(szFolderIn)))break;float time1 = ProcessFolder(hWnd, szFolderIn, false, false);if (time1 < 0)break;float time2 = ProcessFolder(hWnd, szFolderIn, false, true);if (time2 < 0)break;float time3 = ProcessFolder(hWnd, szFolderIn, true, false);if (time3 < 0)break;float time4 = ProcessFolder(hWnd, szFolderIn, true, true);if (time4 < 0)break;TCHAR szResult[2000];_stprintf_s(szResult, ARRAYSIZE(szResult), TEXT("Test times in seconds:\n""%.1f\tBoth sequential\n""%.1f\tParallel pages\n""%.1f\tParallel documents\n""%.1f\tBoth parallel"), time1, time2, time3, time4);MessageBox(hWnd, szResult, TEXT("Recognition Time"), MB_ICONINFORMATION);}break;// Keep rest of the code as is
The LEADTOOLS OCR Auto Recognize Manager supports using multiple threads when processing a job, which can be enabled using the L_OcrAutoRecognizeManagerOptions::UseThreads structure member.
It is also possible to execute each job in its own thread, which is done in the code below using the CreateThread() function.
Add two functions named ProcessFolder and RecognizeTif above the WndProc function and add the following code to them:
struct RECOGNIZE_PARAMS{L_OcrAutoRecognizeManager autoRecognizeManager;TCHAR* pszTifFile;TCHAR* pszPdfFile;};DWORD WINAPI RecognizeTif(void* pParam){RECOGNIZE_PARAMS* pRecParam = (RECOGNIZE_PARAMS*)pParam;L_OcrAutoRecognizeManager_Run(pRecParam->autoRecognizeManager, pRecParam->pszTifFile, pRecParam->pszPdfFile, DOCUMENTFORMAT_PDF, NULL);return 0;}//// FUNCTION: ProcessFolder// Return Value:// < 0 : Error// >= 0 : Elapsed Time//float ProcessFolder(HWND hWnd, TCHAR* pszFolderIn, bool parallelPages, bool parallalDocuments){TCHAR szFolderSearch[1000] = TEXT(""); // Input files search location and pattern_tcscpy_s(szFolderSearch, ARRAYSIZE(szFolderSearch), pszFolderIn);_tcscat_s(szFolderSearch, ARRAYSIZE(szFolderSearch), TEXT("\\*.tif"));WIN32_FIND_DATA FindFileData = { 0 };HANDLE hFind = FindFirstFile(szFolderSearch, &FindFileData);if (INVALID_HANDLE_VALUE == hFind){MessageBox(hWnd, TEXT("No TIF files found in folder"), TEXT("LEADTOOLS Demo"), MB_ICONERROR);return -1;}L_OcrEngine ocrEngine = NULL;L_OcrAutoRecognizeManager autoRecognizeManager = NULL;L_OcrAutoRecognizeManagerOptions autoRecognizeOptions = { 0 };L_OcrDocumentManager ocrDocumentManager = NULL;if (L_OcrEngineManager_CreateEngine(L_OcrEngineType_LEAD, &ocrEngine) != SUCCESS)return -1;L_OcrEngine_Startup(ocrEngine, NULL, TEXT("C:\\LEADTOOLS23\\Bin\\Common\\OcrLEADRuntime"));L_OcrEngine_GetAutoRecognizeManager(ocrEngine, &autoRecognizeManager);autoRecognizeOptions.StructSize = sizeof L_OcrAutoRecognizeManagerOptions;L_OcrAutoRecognizeManager_GetOptions(autoRecognizeManager, &autoRecognizeOptions);autoRecognizeOptions.UseThreads = parallelPages;// Deskew and auto-orient all pages before recognitionautoRecognizeOptions.PreprocessPageCommands = L_OcrAutoPreprocessPageCommands_Deskew | L_OcrAutoPreprocessPageCommands_Rotate;L_OcrAutoRecognizeManager_SetOptions(autoRecognizeManager, &autoRecognizeOptions);// Create PDFs with Image/Text optionL_OcrEngine_GetDocumentManager(ocrEngine, &ocrDocumentManager);DOCWRTPDFOPTIONS pdfOptions;pdfOptions.Options.uStructSize = sizeof DOCWRTPDFOPTIONS;L_OcrDocumentManager_GetFormatOptions(ocrDocumentManager, DOCUMENTFORMAT_PDF, &pdfOptions.Options);// Set the specific PDF options we wantpdfOptions.bImageOverText = true;pdfOptions.PdfProfile = DOCWRTPDFPROFILE_PDF;L_OcrDocumentManager_SetFormatOptions(ocrDocumentManager, DOCUMENTFORMAT_PDF, &pdfOptions.Options);ULONGLONG timeStart = GetTickCount64();int nThreadCount = 0;HANDLE hThreads[MAXIMUM_WAIT_OBJECTS] = { 0 };do{if (nThreadCount >= MAXIMUM_WAIT_OBJECTS){MessageBox(hWnd, TEXT("Too many documents for this test"), TEXT("LEADTOOLS Demo"), MB_ICONERROR);return -1;}TCHAR szTifFile[1024] = TEXT(""); // Input file full location and name_tcscpy_s(szTifFile, ARRAYSIZE(szTifFile), pszFolderIn);_tcscat_s(szTifFile, ARRAYSIZE(szTifFile), TEXT("\\"));_tcscat_s(szTifFile, ARRAYSIZE(szTifFile), FindFileData.cFileName);TCHAR szPdfFile[1024] = TEXT(""); // Output file full location and name_tcscpy_s(szPdfFile, ARRAYSIZE(szPdfFile), szTifFile);_tcscat_s(szPdfFile, ARRAYSIZE(szPdfFile), TEXT(".pdf"));RECOGNIZE_PARAMS param = { 0 };param.autoRecognizeManager = autoRecognizeManager;param.pszTifFile = szTifFile;param.pszPdfFile = szPdfFile;if (parallalDocuments) // Call in a new thread{hThreads[nThreadCount] = CreateThread(NULL, 0, RecognizeTif, ¶m, 0, NULL);nThreadCount++;}else // Sequential documents, call directlyRecognizeTif(¶m);} while (FindNextFile(hFind, &FindFileData) != 0);if (nThreadCount > 0){// Do NOT proceed to shutdown OCR before all threads exitWaitForMultipleObjects(nThreadCount, hThreads, TRUE, INFINITE);// Free all threads handleswhile (nThreadCount > 0){nThreadCount--;CloseHandle(hThreads[nThreadCount]);}}ULONGLONG timeElapsed = GetTickCount64() - timeStart;L_OcrEngine_Shutdown(ocrEngine);L_OcrEngine_Destroy(ocrEngine);return timeElapsed / 1000.0;}
The GetSourceFolder() function can be any function that fills the szFolderIn variable with a valid folder name that contains TIFF files. To display a Select Folder dialog to obtain the folder name, add the following code:
#include <shobjidl_core.h>L_INT GetSourceFolder(HWND hWnd, TCHAR* pszFolder, rsize_t string_size){L_INT nRet = FAILURE;IFileOpenDialog* pFileOpenDialog = NULL;IShellItem* pShellItem = NULL;CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_INPROC_SERVER, IID_IFileOpenDialog, (void**)&pFileOpenDialog);if (pFileOpenDialog){pFileOpenDialog->SetOptions(FOS_PICKFOLDERS);if (SUCCEEDED(pFileOpenDialog->Show(hWnd))){pFileOpenDialog->GetResult(&pShellItem);LPWSTR pFolder = NULL;pShellItem->GetDisplayName(SIGDN_FILESYSPATH, &pFolder);if (pFolder){_tcscpy_s(pszFolder, string_size, pFolder);CoTaskMemFree(pFolder);nRet = SUCCESS;}}}if (pShellItem)pShellItem->Release();if (pFileOpenDialog)pFileOpenDialog->Release();return nRet;}
Run the project by pressing F5, or by selecting Debug -> Start Debugging.
If the steps are followed correctly, the application runs and enables the user to select an input folder. The application then processes each TIFF file in the input folder and creates a new PDF file from it. This is repeated four times using different combinations of sequential and parallel processing.

Note: In the test shown by the image, the best results were obtained when processing in parallel both the directory documents as well as the pages of each document. However, a different set of images or a different computer with a different number of CPU cores, could produce different results. Therefore, it is important to test with the actual images that represent the use case, on the actual hardware where the application will be used.
This tutorial showed how to create a Windows C++ OCR application that uses the LEAD OCR Engine to compare the performance when using different techniques of sequential and parallel processing.