Skip to content

OpenCV 到 ACL Pack 迁移指南

本指南帮助开发者将现有 OpenCV 代码迁移到 Android arm64-v8a / Linux aarch64 上的 ACL Pack。

关键差异

1. 没有 cv::Mat — 原始指针 + Stride

OpenCV 用 cv::Mat 包装图像并自动管理内存。ACL Pack 使用原始指针和显式 stride。

cpp
// OpenCV
cv::Mat src = cv::imread("image.jpg");
cv::Mat dst;
cv::GaussianBlur(src, dst, cv::Size(5, 5), 1.0);

// ACL Pack — 调用方管理内存,stride 单位是字节。
// scalar CPP 模板路径是最接近 OpenCV 风格的入口:一次调用即可
// 覆盖该 tier 支持的所有数据类型与通道数。
int width = 1920, height = 1280, cn = 3;
uint8_t* src = new uint8_t[width * height * cn];
uint8_t* dst = new uint8_t[width * height * cn];
// ... 把图像数据加载进 src ...
acl::filter::gaussianBlur<uint8_t, uint8_t>(
    src, dst, width, height, cn,
    /*srcStride=*/0, /*dstStride=*/0,   // 0 表示按连续内存计算
    /*kRadiusX=*/2, /*kRadiusY=*/2,
    /*sigmaX=*/1.0, /*sigmaY=*/1.0,
    /*constant=*/nullptr,
    acl::BorderType::BORDER_REFLECT_101);
delete[] src;
delete[] dst;

要点:

  • stride 单位是字节,不是像素。连续内存传 0 即可。
  • 调用方负责所有缓冲区的分配与释放。
  • 不会做隐式格式转换 — 你必须清楚自己的数据类型。

2. 模板类型 vs 运行时分发

OpenCV 在运行时确定数据类型(src.depth())。ACL Pack 用编译期模板表达元素类型,而模式类枚举(插值 / 阈值类型 / 边界 / 颜色模式)仍然是运行时参数。

cpp
// OpenCV — 运行时类型 + 运行时模式
cv::threshold(src, dst, 128, 255, cv::THRESH_BINARY);

// ACL Pack — 编译期元素类型 + 运行时 ThreshMode
acl::neon::arithmetic::threshold(
    src_u8, dst_u8, width, height,
    /*thresh=*/128, /*maxVal=*/255, /*minVal=*/0,
    /*srcStride=*/0, /*dstStride=*/0,
    acl::ThreshMode::THRESH_BINARY);

3. 基于命名空间的模块组织

OpenCVACL Pack (NEON)ACL Pack (CPP)
cv::GaussianBluracl::neon::filter::gaussianBluracl::filter::gaussianBlur
cv::resizeacl::neon::geometric::resizeacl::geometric::resize
cv::thresholdacl::neon::arithmetic::thresholdacl::arithmetic::threshold
cv::cvtColoracl::neon::cvtcolor::*acl::cvtcolor::*

ARM64 上追求性能用 acl::neon::*,跨平台 scalar 路径用 acl::{module}::*。注意:scalar 命名空间不含 cpp:: 段,直接位于 acl::{module}:: 下。绘制算子位于 acl::draw::,轮廓分析算子位于 acl::contour::,内存工具算子位于 acl::memory::所有算子声明都集中在单一头文件 <acl/api.h> 中。

Tier 可用性(迁移前请先看)

ACL Pack 出货是单个静态库 libacl.a + include/acl/ 头文件。库里所有算子入口都暴露;实际能调用哪些,由 acl::init(licensePath) 加载的 license tier 决定。tier 之外的算子调用会返回 -1005(ACL_ERR_NOT_LICENSED),且不写入输出缓冲。

  • Starter — 基础 filter / color / geometric / arithmetic / analysis / draw 算子。
  • Pro — Starter + 进阶降噪 / 对比度 / 特征 / 轮廓 / DFT / HSV·Lab 色彩。
  • Business — Pro + 企业级算子(HDR、距离变换、连通域、YUV 工具、SIFT / SURF)。
  • Trial — 仅 1 个示例算子(resize),输入固定 1920×1280(其他尺寸返回 -1006),输出带水印。

每个算子的签名与全量算子目录见 API Reference

迁移示例:完整 pipeline

Before (OpenCV)

cpp
#include <opencv2/opencv.hpp>

void processFrame(const cv::Mat& frame) {
    cv::Mat gray, blurred, edges;

    // BGR → 灰度
    cv::cvtColor(frame, gray, cv::COLOR_BGR2GRAY);

    // 5x5 高斯模糊
    cv::GaussianBlur(gray, blurred, cv::Size(5, 5), 1.5);

    // Canny 边缘检测
    cv::Canny(blurred, edges, 50, 150);

    // 找轮廓
    std::vector<std::vector<cv::Point>> contours;
    cv::findContours(edges, contours, cv::RETR_LIST, cv::CHAIN_APPROX_SIMPLE);

    // 缩放到 640x480
    cv::Mat resized;
    cv::resize(edges, resized, cv::Size(640, 480));
}

After (ACL Pack)

cpp
#include <acl/acl.h>
#include <acl/api.h>   // 所有算子都在这一个头文件里

void processFrame(const uint8_t* bgrData, int width, int height) {
    // 调用方分配并管理所有缓冲区 — ACL Pack 内部不做任何分配。
    uint8_t* rgb     = new uint8_t[width * height * 3];
    uint8_t* gray    = new uint8_t[width * height];
    uint8_t* blurred = new uint8_t[width * height];
    uint8_t* edges   = new uint8_t[width * height];

    // BGR → RGB:显式通道交换(RGB2Gray 期望 RGB 序输入)。
    acl::neon::cvtcolor::channelSwap<acl::BGR2RGB>(
        bgrData, rgb, width, height);

    // RGB → 灰度(默认 LUMA = 0.299R + 0.587G + 0.114B,与 cv::COLOR_*2GRAY 一致)。
    acl::neon::cvtcolor::RGB2Gray<uint8_t>(
        rgb, gray, width, height);

    // 高斯模糊 — NEON 固定 5x5 快速路径(1ch u8,默认边界)。
    acl::neon::filter::gaussianBlur5x5(
        gray, blurred, width, height);

    // Canny 边缘(1ch u8,low=50 high=150,默认 aperture=3)。
    acl::neon::filter::canny(
        blurred, edges, width, height, /*low=*/50, /*high=*/150);

    // 找轮廓 — 仅 CPP,Pro tier。
    // `Point2i` / `HierarchyEntry` / `ContourRetrMode` 都在 `acl::` 下。
    std::vector<std::vector<acl::Point2i>> contours;
    acl::analysis::findContours(
        edges, width, height, /*srcStride=*/0,
        contours, /*hierarchy=*/nullptr,
        acl::CONTOUR_RETR_LIST,
        acl::CONTOUR_CHAIN_APPROX_SIMPLE);

    // 缩放到 640x480(1ch u8,双线性)。
    int dstW = 640, dstH = 480;
    uint8_t* resized = new uint8_t[dstW * dstH];
    acl::neon::geometric::resize<uint8_t>(
        edges, resized, width, height, dstW, dstH,
        /*srcStride=*/0, /*dstStride=*/0,
        acl::InterpMode::LINEAR2D);

    // ... 使用结果 ...

    delete[] rgb;
    delete[] gray;
    delete[] blurred;
    delete[] edges;
    delete[] resized;
}

暂未支持(请继续用 OpenCV)

ACL Pack 已经出货的算子全量清单见 API Reference 的 Operator Catalog。下表是还没做的——这些请继续用 OpenCV。

算子优先级备注
cv::undistort / cv::initUndistortRectifyMapP0相机畸变校正
cv::drawContoursP1轮廓渲染
cv::floodFillP1区域填充
cv::cornerSubPixP2亚像素角点细化
cv::dilate 自定义 kernel 形状P2当前仅矩形
cv::dnn 模块N/A直接用 TFLite / NNAPI
cv::VideoCapture / cv::imwriteN/A用 Android SDK 做 I/O

迁移注意事项

  1. 从热点开始。 先用 profiler 找出 OpenCV 代码里最慢的几步,优先迁移——即使只迁一部分也能见到可衡量的收益,而且可以先在小范围验证集成,再决定是否全面 port。

  2. kernel 是 3×3 / 5×5 / 11×11 时优先用 NEON 固定尺寸入口。 固定尺寸的 NEON Gaussian / box / median 路径比支持任意半径的模板路径快 2-25×。当你需要非 u8 类型或任意参数时,模板化的 acl::{module}::* scalar 路径仍然是正确选择。

完整 API 文档请参见 API Reference
性能基准请参见 Performance Whitepaper