鸿蒙Next集成opencv

交叉编译

鸿蒙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来编译。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,039评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,223评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,916评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,009评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,030评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,011评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,934评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,754评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,202评论 1 309
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,433评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,590评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,321评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,917评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,568评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,738评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,583评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,482评论 2 352

推荐阅读更多精彩内容