OpenCV 到 ACL Pack 迁移指南
本指南帮助开发者将现有 OpenCV 代码迁移到 Android arm64-v8a / Linux aarch64 上的 ACL Pack。
关键差异
1. 没有 cv::Mat — 原始指针 + Stride
OpenCV 用 cv::Mat 包装图像并自动管理内存。ACL Pack 使用原始指针和显式 stride。
// 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 用编译期模板表达元素类型,而模式类枚举(插值 / 阈值类型 / 边界 / 颜色模式)仍然是运行时参数。
// 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. 基于命名空间的模块组织
| OpenCV | ACL Pack (NEON) | ACL Pack (CPP) |
|---|---|---|
cv::GaussianBlur | acl::neon::filter::gaussianBlur | acl::filter::gaussianBlur |
cv::resize | acl::neon::geometric::resize | acl::geometric::resize |
cv::threshold | acl::neon::arithmetic::threshold | acl::arithmetic::threshold |
cv::cvtColor | acl::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)
#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)
#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::initUndistortRectifyMap | P0 | 相机畸变校正 |
cv::drawContours | P1 | 轮廓渲染 |
cv::floodFill | P1 | 区域填充 |
cv::cornerSubPix | P2 | 亚像素角点细化 |
cv::dilate 自定义 kernel 形状 | P2 | 当前仅矩形 |
cv::dnn 模块 | N/A | 直接用 TFLite / NNAPI |
cv::VideoCapture / cv::imwrite | N/A | 用 Android SDK 做 I/O |
迁移注意事项
从热点开始。 先用 profiler 找出 OpenCV 代码里最慢的几步,优先迁移——即使只迁一部分也能见到可衡量的收益,而且可以先在小范围验证集成,再决定是否全面 port。
kernel 是 3×3 / 5×5 / 11×11 时优先用 NEON 固定尺寸入口。 固定尺寸的 NEON Gaussian / box / median 路径比支持任意半径的模板路径快 2-25×。当你需要非
u8类型或任意参数时,模板化的acl::{module}::*scalar 路径仍然是正确选择。
完整 API 文档请参见 API Reference。
性能基准请参见 Performance Whitepaper。