美文网首页
鸿蒙Next集成opencv

鸿蒙Next集成opencv

作者: MambaYong | 来源:发表于2024-08-20 14:26 被阅读0次

交叉编译

鸿蒙Next是不兼容安卓之前编译的so库的,需要利用鸿蒙提供的工具进行交叉编译才行,交叉工具的安装有二种方式:

  • 如果安装了Deveco工具的话,可以在安装目录下找到编译工具,Mac上在应用程序上找到Deveco右键显示包内容,然后找到contents/sdk/HarmonyOS-NEXT-DB3/openharmony/native目录下,里面有二个文件夹build-toolsbuild,其中build/cmake/ohos.toolchain.cmakebuild-tools/cmake/bin/cmake二个文件是我们需要使用的。

  • 也可以手动下载 OpenHarmony SDK选择对应系统的版本即可,SDK分为fullpublic二个版本,这二个版本的SDK中关于c/c++编译的部分是相同的,随便下载一个就行。下载好后进行解压,然后进去到ohos-sdk/darwin/native下,可以看到有buildbuild-tools二个文件夹,这和上面一样,我们同样要用到cmakeohos.toolchain.cmake这二个文件。

    image-20240821101057465.png

opencv源码下载及编译

github上找到opencv的源码下载下载,opencv是支持cmake编译的,然后源码文件夹下新建ohos64build文件夹用来存放编译后的文件。

image-20240821101439732.png

然后进入到ohos64build文件夹下,这里我们采用上面第二种方式的cmake工具在编译,执行如下命令生成makefile文件:

/Library/OpenHarmony4.0.10.5/ohos-sdk/darwin/native/build-tools/cmake/bin/cmake -DCMAKE_TOOLCHAIN_FILE=/Library/OpenHarmony4.0.10.5/ohos-sdk/darwin/native/build/cmake/ohos.toolchain.cmake ..

主要前面的cmake和后面的ohos.toolchain.cmake都是鸿蒙sdk里面的,替换成自己的路径,后面的二个点不能遗漏

最后执行结果如下,省略了很多中间过程:

image-20240821103123256.png

然后执行make && make install即可编译出so库,编译完成后文件都在install文件夹下,其中so库在lib文件下:

image-20240821105009582.png

头文件在include文件夹下:

image-20240821105516195.png

上面的命令编译出来的so库默认架构是ARM aarch64 ,如果要编译 arm32 的需要在cmake时加上参数 -DOHOS_ARCH=armeabi-v7a

集成

集成到项目里面是有多种方式的,可以通过源码,也可以通过so库的方式,这里我们采用刚刚编译出的so库来集成,首先利用Deveco新建一个native项目,新建项目的过程请看官网介绍,这里不在赘述,我们要实现的是利用opencv来灰度处理图片,效果如下:

image-20240821110302524.png

查看so库的soname

由于我们编译的是动态库,动态库的引用是通过soname来查找的,我们需要查看编译出来so库的soname,然后把so库重命名为对应的名字,查看soname的命令是如下:

/Library/OpenHarmony4.0.10.5/ohos-sdk/darwin/native/llvm/bin/llvm-readobj -d libopencv_calib3d.so.4.8.1

细节如下,可以看到soname名字是so.408结尾,所以需要把lib下的所有库后缀为so.4.8.1的重命名为so.408

image-20240821110939498.png

集成到项目

so库集成

把上面的重命名的so库放到entry下的lib文件夹下,具体如下图所示:

image-20240821111611791.png

头文件集成

src/main/cpp下面新建一个thirdparty文件夹,然后新建opencv文件夹以及对应的架构文件夹,同时新建一个include文件夹,在把对应的头文件放进去,我们把上面编译出来的整个opencv4直接拷贝进去,具体如下图所示:

image-20240821111958931.png

完成上面的后,需要在cpp下的CMakeLists.txt增加如下内容,以便应用so和头文件:

image-20240821112312935.png

nopi代码的编写

native侧实现

鸿蒙ArkTSc/c++交互是通过nopi来实现的,和安卓的jni类似,需要编写中间层代码,鸿蒙采用的是node来实现的,具体的用法请看官网,这里仅给出实现:

#include "napi/native_api.h"
#include "opencv2/opencv.hpp"
#include <vector>
static napi_value ProcessImage(napi_env env, napi_callback_info info)
{
    size_t argc = 1;
    napi_value args[1] = {nullptr};
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
    if (argc < 1) {
        napi_throw_error(env, nullptr, "图片数据参数缺失");
        return nullptr;
    }
    
    // 检查参数是否是 ArrayBuffer 类型
    bool isArrayBuffer = false;
    napi_status status = napi_is_arraybuffer(env, args[0], &isArrayBuffer);
    if (status != napi_ok || !isArrayBuffer) {
        napi_throw_error(env, nullptr, "参数不是有效的 ArrayBuffer");
        return nullptr;
    }
    // 获取传入的图片数据 (Buffer)
    void* bufferData;
    size_t bufferLength;
    status = napi_get_arraybuffer_info(env, args[0], &bufferData, &bufferLength);
    if (status != napi_ok) {
        napi_throw_error(env, nullptr, "无法获取 ArrayBuffer 信息");
        return nullptr;
    }
     // 将 Buffer 数据转换为 OpenCV Mat 对象
    std::vector<unsigned char> imgData((unsigned char*)bufferData, (unsigned char*)bufferData + bufferLength);
    cv::Mat img = cv::imdecode(imgData, cv::IMREAD_COLOR);
    if (img.empty()) {
        napi_throw_error(env, nullptr, "无法解码图像数据");
        return nullptr;
    }
    // 转换为灰度图
    cv::Mat grayImg;
    cv::cvtColor(img, grayImg, cv::COLOR_BGR2GRAY);
    // 将处理后的图片转换回 Buffer
    std::vector<unsigned char> encodedImg;
    cv::imencode(".png", grayImg, encodedImg);
   // 创建一个新的 ArrayBuffer 来存储编码后的图像数据
    napi_value resultArrayBuffer;
    void* arrayBufferData = nullptr;
    status = napi_create_arraybuffer(env, encodedImg.size(), &arrayBufferData, &resultArrayBuffer);
    if (status != napi_ok) {
        napi_throw_error(env, nullptr, "无法创建 ArrayBuffer");
        return nullptr;
    }

    // 将图像数据复制到 ArrayBuffer 中
    std::memcpy(arrayBufferData, encodedImg.data(), encodedImg.size());

    // 返回 ArrayBuffer
    return resultArrayBuffer;

}
static napi_value Add(napi_env env, napi_callback_info info)
{
    size_t argc = 2;
    napi_value args[2] = {nullptr};

    napi_get_cb_info(env, info, &argc, args , nullptr, nullptr);

    napi_valuetype valuetype0;
    napi_typeof(env, args[0], &valuetype0);

    napi_valuetype valuetype1;
    napi_typeof(env, args[1], &valuetype1);

    double value0;
    napi_get_value_double(env, args[0], &value0);

    double value1;
    napi_get_value_double(env, args[1], &value1);

    napi_value sum;
    napi_create_double(env, value0 + value1, &sum);

    return sum;

}

EXTERN_C_START
static napi_value Init(napi_env env, napi_value exports)
{
    napi_property_descriptor desc[] = {
        { "add", nullptr, Add, nullptr, nullptr, nullptr, napi_default, nullptr },
        { "processImage", nullptr, ProcessImage, 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 = "entry",
    .nm_priv = ((void*)0),
    .reserved = { 0 },
};

extern "C" __attribute__((constructor)) void RegisterEntryModule(void)
{
    napi_module_register(&demoModule);
}

上面的add方法是官方新建项目生成的,可以去掉,这里主要是接收ArkUI侧传递过来的图片buffer数据,然后利用opencv进行灰度处理,在将灰度图片的buffer数据回传给ArkUI

在types/libentry下的Index.d.ts下面新增processImage方法,具体如下:

export const add: (a: number, b: number) => number;
export const processImage: (imageBuffer: ArrayBuffer) => ArrayBuffer;

ArkUI侧的实现

ArkUI侧的全部实现如下:

import { hilog } from '@kit.PerformanceAnalysisKit';
import testNapi from 'libentry.so';
import fileIO from '@ohos.fileio';
import { resourceManager } from '@kit.LocalizationKit';
import { BusinessError } from '@kit.BasicServicesKit';
const context : Context = getContext(this);
// 获取resourceManager资源管理器
const resourceMgr : resourceManager.ResourceManager = context.resourceManager;
// 将 ArrayBuffer 转换为 Base64 字符串的辅助函数
function arrayBufferToBase64(buffer: ArrayBuffer): string {
  const base64chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
  let base64 = '';
  const bytes = new Uint8Array(buffer);
  const byteLength = bytes.byteLength;

  for (let i = 0; i < byteLength; i += 3) {
    const b1 = bytes[i];
    const b2 = i + 1 < byteLength ? bytes[i + 1] : 0;
    const b3 = i + 2 < byteLength ? bytes[i + 2] : 0;

    const chunk = (b1 << 16) | (b2 << 8) | b3;

    base64 += base64chars.charAt((chunk >> 18) & 0x3F);
    base64 += base64chars.charAt((chunk >> 12) & 0x3F);
    base64 += base64chars.charAt((chunk >> 6) & 0x3F);
    base64 += base64chars.charAt(chunk & 0x3F);
  }

  // 根据字节数计算需要添加的填充符号
  const padding = byteLength % 3;
  if (padding > 0) {
    base64 = base64.slice(0, padding - 3) + '==='.slice(padding);
  }

  return base64;
}
@Entry
@Component
struct Index {
  @State image: PixelMap | undefined = undefined;
  @State originalImageSrc: string = 'opencv_test.png';
  @State base64Img: string = '';
  @State processedImageSrc: string = '';
  async loadImageBuffer(imagePath: string): Promise<ArrayBuffer> {
    return new Promise((resolve, reject) => {
      resourceMgr.getRawFileContent(imagePath).then((fileData : Uint8Array) => {
        console.log("Succeeded in getting RawFileContent")
        // 获取图片的ArrayBuffer
        const buffer = fileData.buffer.slice(0);
        resolve(buffer);
      }).catch((err : BusinessError) => {
        reject('Failed to get RawFileContent');
      });
    });
  };

  build() {
    Row() {
      Column() {
        Image($rawfile(this.originalImageSrc))
          .width(200)
        Button('点击后利用opencv来灰度图片')
          .onClick(async () => {
            hilog.info(0x0000, 'testTag', '这是利用CMake编译的so库的结果:Test NAPI 10 + 15 = %{public}d', testNapi.add(10,15));
            hilog.info(0x0000, 'testTag', "点击了");
            // 先加载图片的buffer
            this.loadImageBuffer(this.originalImageSrc).then( buffer => {
              // 利用opencv进行灰度处理
              let new_buffer = testNapi.processImage(buffer);
              let base64 = arrayBufferToBase64(new_buffer);
              this.base64Img = base64;
              hilog.info(0x0000, 'testTag', "图片转成的base64 是%{public}s",base64);
            }
            ).catch( (msg:string) =>  {
              hilog.info(0x0000, 'testTag', '发生了错误%{public}s',msg);
            }
            );
          })
        Image(`data:image/png;base64,${this.base64Img}`).width(200)
      }
      .width('100%')
    }
    .height('100%')
  }
}

点击按钮的实现首先利用ResourceManager来获取图片的buffer数据,然后调用native侧的processImage方法或者灰度处理后的buffer,然后转成base64,最后利用Image组件来显示base64的图片,图片放到src/main/resources/rawfile目录下。

总结

本位主要是讲解了如何利用鸿蒙提供的工具来交叉编译三方库,然后集成到native项目中,当然也可以使用第三方工具lycium来编译。

相关文章

网友评论

      本文标题:鸿蒙Next集成opencv

      本文链接:https://www.haomeiwen.com/subject/gwwvkjtx.html