OpenHarmony整合OCR三方庫實現文字提取

OpenHarmony開發者社群發表於2022-11-15


1. 簡介

Tesseract(Apache 2.0 License)是一個可以進行影像OCR識別的C++庫,可以跨平臺執行 。本樣例基於Tesseract庫進行適配,使其可以執行在OpenAtom OpenHarmony(以下簡稱“OpenHarmony”)上,並新增N-API介面供上層應用呼叫,這樣上層應用就可以使用Tesseract提供的相關功能。

2. 效果展示

動物圖片識別文字

undefined

身份資訊識別

undefined

提取文字資訊到本地檔案

undefined

相關程式碼已經上傳至SIG倉庫,連結如下:

3. 目錄結構

undefined

4. 呼叫流程

undefined

呼叫過程主要涉及到三方面,首先應用層實現樣例的效果,包括頁面的佈局和業務邏輯程式碼;中間層主要起橋樑的作用,提供N-API介面給應用呼叫,再透過三方庫的介面去呼叫具體的實現;Native層使用了三方庫Tesseract提供具體的實現功能。

5. 原始碼分析

本樣例原始碼的分析主要涉及到兩個方面,一方面是N-API介面的實現,另一方面是應用層的頁面佈局和業務邏輯。

N-API實現

1. 首先在index.d.ts檔案中定義好介面

/**
 * 初始化文字識別引擎
 * @param lang 識別的語言, eg:eng、chi_sim、 eng+chi_sim,為Null或不傳則為中英文(eng+chi_sim)
 * @param trainDir 訓練模型目錄,為Null或不傳則為預設目錄
 *
 * @return 初始化是否成功 0=>成功,-1=>失敗
 */
export const initOCR: (lang: string, trainDir: string) => Promise<number>;
export const initOCR: (lang: string, trainDir: string, callback: AsyncCallback<number>) => void;
/**
 * 開始識別
 * @param imagePath 圖片路徑(當前支援的圖片格式為png, jpg, tiff)
 *
 * @return 識別結果
 */
export const startOCR: (imagePath: string) => Promise<string>;
export const startOCR: (imagePath: string, callback: AsyncCallback<string>) => void;
/**
 * 銷燬資源
 */
export const destroyOCR: () => void;

程式碼中可以看出N-API介面initOCR和startOCR都採用了兩種方式,一種是Promise,一種是Callback的方式。在樣例的應用層,使用的是它們的Callback方式。

2.註冊N-API模組和介面

EXTERN_C_START
static napi_value Init(napi_env env, napi_value exports) {
napi_property_descriptor desc[] = {
{
"initOCR", nullptr, InitOCR, nullptr, nullptr, nullptr, napi_default, nullptr
},
{
"startOCR", nullptr, StartOCR, nullptr, nullptr, nullptr, napi_default, nullptr
},
{
"destroyOCR", nullptr, DestroyOCR, nullptr, nullptr, nullptr, napi_default, nullptr
},
{
};
napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
return exports;
}
EXTERN_C_END
static napi_module demoModule = {
.nm_version = 1,
.nm_flags = 0,
.nm_filename = nullptr,
.nm_register_func = Init,
.nm_modname = "tesseract",
.nm_priv = ((void *)0),
.reserved = {
},
};
extern "C" __attribute__((constructor)) void RegisterHelloModule(void) {
napi_module_register(& demoModule);
}

透過nm_modname定義模組名,nm_register_func註冊介面函式,在Init函式中指定了JS中initOCR,startOCR,destroyOCR對應的本地實現函式,這樣就可以在對應的本地實現函式中呼叫三方庫Tesseract的具體實現了。

3.以startOCR的Callback方式為例介紹N-API中的具體實現

static napi_value StartOCR(napi_env env, napi_callback_info info) {
    OH_LOG_ERROR(LogType::LOG_APP, "OCR StartOCR 111");
    size_t argc = 2;
    napi_value args[2] = { nullptr };
  //1. 獲取引數
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
    //2. 共享資料
    auto addonData = new StartOCRAddOnData{
        .asyncWork = nullptr,
    };
    //3. N-API型別轉成C/C++型別
    char imagePath[1024] = { 0 };
    size_t length = 0;
    napi_get_value_string_utf8(env, args[0], imagePath, 1024, &length);
    addonData->args0 = string(imagePath);
    napi_create_reference(env, args[1], 1, &addonData->callback);
    //4. 建立async work
    napi_value resourceName = nullptr;
    napi_create_string_utf8(env, "startOCR", NAPI_AUTO_LENGTH, &resourceName);
    napi_create_async_work(env, nullptr, resourceName, executeStartOCR, completeStartOCRForCallback, (void *)addonData, &addonData->asyncWork);
    //將建立的async work加到佇列中,由底層排程執行
    napi_queue_async_work(env, addonData->asyncWork);
    napi_value result = 0;
    napi_get_null(env, &result);
    return result;
}

首先透過napi_get_cb_info方法獲取JS側傳入的引數資訊,將引數轉成C++對應的型別,然後建立非同步工作,非同步工作的方法引數中包含,執行的函式以及函式執行完成的回撥函式。

我們看一下執行函式

static void executeStartOCR(napi_env env, void* data) {
    //透過data來獲取資料
    StartOCRAddOnData * addonData = (StartOCRAddOnData *)data;
    napi_value resultValue;
    try {
        if (api != nullptr) {
            //呼叫具體的實現,讀取圖片畫素
            PIX * pix = pixRead((const char*)addonData->args0.c_str());
            //設定api的圖片畫素
            api->SetImage(pix);
            //呼叫文字提取介面,獲取圖片中的文字
            char * result = api->GetUTF8Text();
            addonData->result = result;
            //釋放資源
            pixDestroy (& pix);
            delete[] result;
        }
    } catch (std::exception e) {
        std::string error = "Error: ";
        if (initResult != 0) {
            error += "please first init tesseractocr.";
        } else {
            error += e.what();
        }
        addonData->result = error;
    }
}

這個方法中透過data獲取JS傳入的引數,然後呼叫Tesseract庫中提供的介面,呼叫具體的文字提取功能,獲取圖片中的文字。

執行完成後,會回撥到completeStartOCRForCallback,在這個方法中會將執行函式中返回的結果轉換為JS的對應型別,然後透過Callback的方式返回。

static void completeStartOCRForCallback(napi_env env, napi_status status, void * data) {
    StartOCRAddOnData * addonData = (StartOCRAddOnData *)data;
    napi_value callback = nullptr;
    napi_get_reference_value(env, addonData->callback, &callback);
    napi_value undefined = nullptr;
    napi_get_undefined(env, &undefined);
    napi_value result = nullptr;
    napi_create_string_utf8(env, addonData->result.c_str(), addonData->result.length(), &result);
    //執行回撥函式
    napi_value returnVal = nullptr;
    napi_call_function(env, undefined, callback, 1, &result, &returnVal);
    //刪除napi_ref物件
    if (addonData->callback != nullptr) {
        napi_delete_reference(env, addonData->callback);
    }
    //刪除非同步工作項
    napi_delete_async_work(env, addonData->asyncWork);
    delete addonData;
}

應用層實現

應用層主要分為三個模組:動物圖片文字識別,身份資訊識別,提取文字到本地檔案

1. 動物圖片文字識別

build() {
    Column() {
      Row() {
        Text('點選圖片進行文字提取  提取結果 :').fontSize('30fp').fontColor(Color.Blue)
        Text(this.ocrResult).fontSize('50fp').fontColor(Color.Red)
      }.margin('10vp').height('10%').alignItems(VerticalAlign.Center)
      Grid() {
        ForEach(this.images, (item, index) => {
          GridItem() {
            AnimalItem({
              path1: item[0],
              path2: item[1]
            });
          }
        })
      }
      .padding({left: this.columnSpace, right: this.columnSpace})
      .columnsTemplate("1fr 1fr 1fr")      // Grid寬度均分成3份
      .rowsTemplate("1fr 1fr")     // Grid高度均分成2份
      .rowsGap(this.rowSpace)                  // 設定行間距
      .columnsGap(this.columnSpace)            // 設定列間距
      .width('100%')
      .height('90%')
    }
    .backgroundColor(Color.Pink)
  }

佈局主要使用了Grid的網格佈局,每個Item都是對應的圖片,透過點選圖片可以對點選圖片進行文字提取,將提取出的文字顯示在標題欄。

2. 身份資訊識別

build() {
    Row() {
      Column() {
        Image('/common/idImages/aobamao.jpg')
          .onClick(() => {
            //點選圖片進行資訊識別
            console.log('OCR begin dialog open 111');
            this.ocrDialog.open();
            ToolUtils.ocrResult(ToolUtils.aobamao, (result) => {
              console.log('111 OCR result = ' + result);
              this.result = result;
              this.ocrDialog.close();
            });
          })
          .margin('10vp')
          .objectFit(ImageFit.Auto)
          .height('50%')
        Image('/common/idImages/weixiaobao.jpg')
          .onClick(() => {
            //點選圖片進行資訊識別
            this.ocrDialog.open();
            ToolUtils.ocrResult(ToolUtils.weixiaobao, (result) => {
              console.log('111 OCR result = ' + result);
              this.result = result;
              this.ocrDialog.close();
            });
          })
          .margin('10vp')
          .objectFit(ImageFit.Auto)
          .height('50%')
      }
      .width(this.screenWidth/2)
      .padding('20vp')
      Column() {
        Text(this.title).height('10%').fontSize('30fp').fontColor(this.titleColor)
        Column() {
          Text(this.result)
            .fontColor('#0000FF')
            .fontSize('50fp')
        }.justifyContent(FlexAlign.Center).alignItems(HorizontalAlign.Center).height('90%')
      }
      .justifyContent(FlexAlign.Start)
      .width('50%')
    }
    .width('100%')
    .height('100%')
  }

身份資訊識別的佈局最外層是一個水平佈局,分為左右兩部分,左邊的子佈局是垂直佈局,裡面是兩張不同的身份證圖片,右邊子佈局也是垂直佈局,主要是標題區和識別結果的內容顯示區。

3. 提取文字到本地檔案

Row() {
      Column() {
        Image('/common/save2FileImages/testImage1.png')
          .onClick(() => {
            //點選圖片進行資訊識別
            ToolUtils.ocrResult(ToolUtils.testImage1, (result) => {
              let path = this.dir + 'ocrresult1.txt';
              try {
                let fd = fileio.openSync(path, 0o100 | 0o2, 0o666);
                fileio.writeSync(fd, result);
                fileio.closeSync(fd);
                this.displayText = '檔案寫入' + path;
              } catch (e) {
                console.log('OCR fileio error = ' + e);
              }
            });
          })
        Image('/common/save2FileImages/testImage2.png')
          .onClick(() => {
            //點選圖片進行資訊識別
            ToolUtils.ocrResult(ToolUtils.testImage2, (result) => {
              let path = this.dir + 'ocrresult2.txt';
              let fd = fileio.openSync(path, 0o100 | 0o2, 0o666);
              fileio.writeSync(fd, result);
              fileio.closeSync(fd);
              this.displayText = '檔案寫入' + path;
            });
          })
      }
      Column() {
        Text(this.title)
        Column() {
          Text(this.displayText)
        }
      }
    }

這個功能首先透過介面識別出圖片中的文字,然後再透過fileio的能力將文字寫入檔案中。

6. 總結

樣例透過Native的方式將C++的三方庫整合到應用中,透過N-API方式提供介面給上層應用呼叫。對於依賴三方庫能力的應用,都可以使用這種方式來進行,移植三方庫到Native,透過N-API提供介面給應用呼叫。

關於樣例開發,還分享過《 如何利用OpenHarmony ArkUI的Canvas元件實現塗鴉功能? 》、《 如何透過OpenHarmony的音訊模組實現錄音變速功能? 》歡迎感興趣的開發者進行了解並交流樣例開發經驗。

undefined



來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70011554/viewspace-2923390/,如需轉載,請註明出處,否則將追究法律責任。

相關文章