目前OpenCV的NN模块中已经有Halide一部分代码,可以通过 net.setPreferableBackend(DNN_BACKEND_HALIDE); 告诉OpenCV NN引擎尽可能使用Halide进行计算。除非以后OpenCV完全实现Halide的运行机制,否则在图像处理任务中,Halide与OpenCV协同工作是不可避免的。这篇博客主要介绍Halide的图像类型和OpenCV图像类型的相互转化。
一、代码示例
代码胜千言:
#include <stdio.h>
#include <Halide.h>
#include <halide_image_io.h>
#include <opencv2/opencv.hpp>
int main(int argc, char* argv[]) {
#ifdef HAVE_OPENCV
cv::Mat image = cv::imread("images/rgb.png");
cv::cvtColor(image, image, cv::COLOR_BGR2RGB);
cv::imshow("in", image);
cv::waitKey(10);
Halide::Buffer<uint8_t> input(Halide::Buffer<uint8_t>::make_interleaved(image.data, image.cols, image.rows, image.channels()));
// If the OpenCV matrix has padding between the rows, the longer form is
// a halide_dimension_t is the min coordinate, the extent, and then the stride/step in that dimension.
//halide_dimension_t shape[3] = { {0, image.cols, image.step1(1)},
// {0, image.rows, image.step1(0)},
// {0, image.channels(), 1}
//};
//Halide::Buffer<uint8_t> buffer(image.data, 3, shape);
#else
Halide::Buffer<uint8_t> input = Halide::Tools::load_image("images/rgb.png");
#endif
Halide::Func brighter;
Halide::Var x, y, c;
Halide::Expr value = input(x, y, c);
value = Halide::cast<float>(value);
value = value * 1.5f;
value = Halide::min(value, 255.0f);
value = Halide::cast<uint8_t>(value);
brighter(x, y, c) = value;
Halide::Buffer<uint8_t> output =
brighter.realize(input.width(), input.height(), input.channels());
#ifdef HAVE_OPENCV
cv::Mat image_out = cv::Mat::zeros(output.height(), output.width(), CV_8UC3);
for (int i = 0; i < output.height(); ++i) {
for (int j = 0; j < output.width(); ++j) {
for (int n = 0; n < output.channels(); ++n) {
image_out.at<uchar>(i, j*output.channels()+n) = (uchar)output(j, i, n);
}
}
}
cv::imshow("out", image_out);
cv::waitKey(10);
#else
Halide::Tools::save_image(output, "brighter.png");
#endif
return 0;
}
代码上传到了这个仓库:
P-Chao / Halide-Zoon
halide learning demo collection
二、OpenCV的cv::Mat转化为Halide Buffer
这个直接用Halide Buffer的构造就可以了
Halide::Buffer<uint8_t> input(Halide::Buffer<uint8_t>::make_interleaved(image.data, image.cols, image.rows, image.channels()));
如果数据buffer本身有ROI或者stride之类的存储规则,那么也有初始化api可以使用:
// If the OpenCV matrix has padding between the rows, the longer form is
// a halide_dimension_t is the min coordinate, the extent, and then the stride/step in that dimension.
halide_dimension_t shape[3] = { {0, image.cols, image.step1(1)},
{0, image.rows, image.step1(0)},
{0, image.channels(), 1}
};
Halide::Buffer<uint8_t> buffer(image.data, 3, shape);
博客最后会对Halide::Buffer初始化api进行一下总结
三、Halide Buffer转化为OpenCV的cv::Mat
如果时单通道图可以memcpy,但是多通道图没找到写起来简单的方法,因为Halide Buffer里面数据存储顺序和cv::Mat的数据存取规则相距甚远,只有逐个pixel去取了
cv::Mat image_out = cv::Mat::zeros(output.height(), output.width(), CV_8UC3);
for (int i = 0; i < output.height(); ++i) {
for (int j = 0; j < output.width(); ++j) {
for (int n = 0; n < output.channels(); ++n) {
image_out.at<uchar>(i, j*output.channels()+n) = (uchar)output(j, i, n);
}
}
}
四、Halide Buffer初始化方式总结
参考链接:HalideにおけるバッファHalide::Buffer<T>の操作(HalideとOpenCVの相互変換)
首先简单看下Buffer有哪些属性:
Func grad; grad(x, y, c) = cast<uint8_t>((x + y) % 256); Halide::Buffer<uint8_t> output = realize(256,256); output.width();//宽 output.height();//高 output.channels()//通道 output.dimensions()//维度 output2.size_in_bytes()//字节(长x宽x通道xsizeof(T)). uchar* data = output2.begin();//RAW数据的开头 uchar* data = output2.data();//RAW数据的开头,和上面是一样的 uchar* data = output2.end();//RAW数据的结尾
下面开始看Halide::Buffer的初始化方法:
1. 使用Halide::Buffer结构初始化
构造函数:
Halide::Buffer<uint8_t> input(Halide::Buffer<uint8_t>::make_interleaved(image.data, image.cols, image.rows, image.channels()));
2. 初始化时只指定buffer size,而不给出数据
void halideInitTest2(Mat& dest)
{
Var x, y, c;
Func grad;
grad(x, y, c) = cast<uint8_t>((x + y) % 256);
Halide::Buffer<uint8_t> output(dest.cols, dest.rows, dest.channels());
grad.realize(output);
uchar* data = output.data();
memcpy(dest.data, data, output.size_in_bytes());
}
3. 根据数据大小和数据头部指针初始化
void halideInitTest3(Mat& dest)
{
Var x, y, c;
Func grad;
grad(x, y, c) = cast<uint8_t>((x + y) % 256);
//dest.ptr<uchar>(0)はOpenCVの画像データの先頭ポインタ
Halide::Buffer<uint8_t> output(dest.ptr<uchar>(0), dest.cols, dest.rows, dest.channels());
grad.realize(output);
}
4. 先申请内存,再逐个pixel初始化
void halideInitTest6(Mat& src, Mat& dest)
{
Var x, y, c;
Halide::Buffer<uint8_t> input(src.cols, src.rows, src.channels());
for (int j = 0; j < src.rows; j++)
{
for (int i = 0; i < src.cols; i++)
{
for (int n = 0; n < src.channels(); n++)
{
input(i, j, n) = src.at<uchar>(j, i*src.channels() + n);
}
}
}
}
OK,See You Next Chapter!