Multi-thread OCR with the Auto Recognize Manager - Windows C DLL

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

Required Knowledge

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.

Create the Project and Add LEADTOOLS References

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.

Set the License File

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.

Add the Multi-threading OCR Code

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 folder 
   if (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 recognition 
   autoRecognizeOptions.PreprocessPageCommands = L_OcrAutoPreprocessPageCommands_Deskew | L_OcrAutoPreprocessPageCommands_Rotate; 
   L_OcrAutoRecognizeManager_SetOptions(autoRecognizeManager, &autoRecognizeOptions); 
   // Create PDFs with Image/Text option 
   L_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 want 
   pdfOptions.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, &param, 0, NULL); 
         nThreadCount++; 
      } 
      else // Sequential documents, call directly 
         RecognizeTif(&param); 
 
   } while (FindNextFile(hFind, &FindFileData) != 0); 
 
   if (nThreadCount > 0) 
   { 
      // Do NOT proceed to shutdown OCR before all threads exit 
      WaitForMultipleObjects(nThreadCount, hThreads, TRUE, INFINITE); 
      // Free all threads handles 
      while (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

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.

The application displays OCR progress

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.

Wrap-up

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.

See Also

Help Version 23.0.2024.5.22
Products | Support | Contact Us | Intellectual Property Notices
© 1991-2024 LEAD Technologies, Inc. All Rights Reserved.

Products | Support | Contact Us | Intellectual Property Notices
© 1991-2023 LEAD Technologies, Inc. All Rights Reserved.