概述
OpenCV 中的 carotene 对于 armv7优化较好,而 armv8下则是 NEON 实现。TNN 提供了一套图像预处理接口并且进行了汇编优化。下面以 NV21TOBGR 为例进行介绍。
MatUtils
无成员变量,全部为静态函数。
public:
//copy cpu <-> device, cpu<->cpu, device<->device, src and dst dims must be equal.
static Status Copy(Mat& src, Mat& dst, void* command_queue);
//src and dst device type must be same. when param scale_w or scale_h is 0, it is computed as
// (double)dst.GetWidth() / src.GetWidth() or (double)dst.GetHeight() / src.GetHeight().
static Status Resize(Mat& src, Mat& dst, ResizeParam param, void* command_queue);
//src and dst device type must be same. when param width or height is 0, it is equal to
//dst.GetWidth() or dst.GetHeight().
static Status Crop(Mat& src, Mat& dst, CropParam param, void* command_queue);
//src and dst device type must be same.
static Status WarpAffine(Mat& src, Mat& dst, WarpAffineParam param, void* command_queue);
//src and dst device type must be same.
static Status CvtColor(Mat& src, Mat& dst, ColorConversionType type, void* command_queue);
//src and dst device type must be same. param top, bottom, left and right must be non-negative.
static Status CopyMakeBorder(Mat& src, Mat& dst, CopyMakeBorderParam param, void* command_queue);
MatUtils::CvtColor
调用 CheckSrcAndDstMat 输入和输出变量的设备是否相同,输入尺寸是否有效。
构造一个转换器并调用其缩放函数。
auto ret = CheckSrcAndDstMat(src, dst, true, false, true);
if (ret != TNN_OK) {
return ret;
}
如果输出矩阵为空可申请内存,若已有内存则不能比输入小。
if (dst.GetData() == nullptr) {
// set dst size by src size and cvt type
DimsVector dims = src.GetDims();
dims[1] = GetCvtColorDstChannel(type);
dst = Mat(dst.GetDeviceType(), dst.GetMatType(), dims);
} else {
if (dst.GetWidth() < src.GetWidth() || dst.GetHeight() < src.GetHeight() ||
dst.GetChannel() < GetCvtColorDstChannel(type)) {
return Status(TNNERR_PARAM_ERR, "cvt color dst size too small");
}
}
MAT_CONVERTER_PREPARATION 可在必要时为输出申请内存,并创建一个 MatConverterAcc 对象。
MAT_CONVERTER_PREPARATION(src.GetDeviceType());
return converter->CvtColor(src, dst, type, command_queue);
CheckSrcAndDstMat
检查输入输出的设备类型是否一致、Mat 类型是否一致,以及输入尺寸是否正常。
if (check_device_type && (src.GetDeviceType() != dst.GetDeviceType())) {
return Status(TNNERR_PARAM_ERR, "src and dst DeviceType not equal");
}
if (check_mat_type && (src.GetMatType() != dst.GetMatType())) {
return Status(TNNERR_PARAM_ERR, "src and dst MatType not equal");
}
if (check_src_size && (src.GetWidth() <= 0 || src.GetHeight() <= 0)) {
return Status(TNNERR_INVALID_INPUT, "src size is zero or negnative");
}
return TNN_OK;
MAT_CONVERTER_PREPARATION
MatConverterManager::Shared 返回一个 MatConverterManager 单例。
MatConverterManager::CreateMatConverterAcc 首先从字典中查找,若没有则创建一个。
#define MAT_CONVERTER_PREPARATION(device_type)
if (dst.GetData() == nullptr) {
dst = Mat(dst.GetDeviceType(), dst.GetMatType(), dst.GetDims());
}
auto converter = MatConverterManager::Shared()->CreateMatConverterAcc(device_type);
if (!converter) {
return Status(TNNERR_INIT_LAYER, "image converter is nil, check device type");
}
MatConverterManager
拥有一个 MatConverterAccCreater 字典,可以实现反射。
public:
static std::shared_ptr<MatConverterManager>& Shared();
MatConverterManager();
~MatConverterManager();
std::shared_ptr<MatConverterAcc> CreateMatConverterAcc(DeviceType device_type);
int RegisterMatConverterAccCreater(DeviceType type, std::shared_ptr<MatConverterAccCreater> creater);
private:
std::map<DeviceType, std::shared_ptr<MatConverterAccCreater>> converter_creater_map_;
MatConverterManager::Shared
借助 std::once_flag 和 std::call_once 实现线程安全的单例模式。
static std::once_flag once;
static std::shared_ptr<MatConverterManager> g_global_blob_converter_manager;
std::call_once(once, []() { g_global_blob_converter_manager = std::make_shared<MatConverterManager>(); });
return g_global_blob_converter_manager;
MatConverterManager::CreateMatConverterAcc
在converter_creater_map_
中查找设备类型,如果有相应的构造者则调用其创建函数。
auto iter = converter_creater_map_.find(device_type);
if (iter != converter_creater_map_.end()) {
return iter->second->CreateMatConverterAcc();
}
return nullptr;
MatConverterManager::RegisterMatConverterAccCreater
向converter_creater_map_
字典中添加设备的构造者。
auto iter = converter_creater_map_.find(type);
if (iter != converter_creater_map_.end()) {
LOGE("Error: device_type(%d) cannot be registered twicen", type);
return 1;
}
if (!creater) {
LOGE("Error: MatConverterAccCreater is nil device_type(%d)n", type);
return 1;
}
converter_creater_map_[type] = creater;
return 0;
MatConverterAccCreater
为工厂方法。
public:
virtual ~MatConverterAccCreater(){};
virtual std::shared_ptr<MatConverterAcc> CreateMatConverterAcc() = 0;
DECLARE_MAT_CONVERTER_CREATER
定义具体设备的工厂。
#define DECLARE_MAT_CONVERTER_CREATER(device)
class device##MatConverterAccCreater : public MatConverterAccCreater {
public:
virtual ~device##MatConverterAccCreater(){};
virtual std::shared_ptr<MatConverterAcc> CreateMatConverterAcc() {
return std::make_shared<device##MatConverterAcc>();
};
}
REGISTER_MAT_CONVERTER
以定义变量的形式,向全局字典中注册特定设备的矩阵转换器。
#define REGISTER_MAT_CONVERTER(device, device_type)
MatConverterAccRegister<device##MatConverterAccCreater> g_mat_converter_##device(device_type)
MatConverterAcc
6种操作的接口。
public:
MatConverterAcc() {
OMP_SET_THREADS_(1);
};
virtual ~MatConverterAcc(){};
virtual Status Copy(Mat& src, Mat& dst, void* command_queue = NULL) = 0;
virtual Status Resize(Mat& src, Mat& dst, ResizeParam param, void* command_queue = NULL) = 0;
virtual Status Crop(Mat& src, Mat& dst, CropParam param, void* command_queue = NULL) = 0;
virtual Status WarpAffine(Mat& src, Mat& dst, WarpAffineParam param, void* command_queue = NULL) = 0;
virtual Status CvtColor(Mat& src, Mat& dst, ColorConversionType type, void* command_queue = NULL) = 0;
virtual Status CopyMakeBorder(Mat& src, Mat& dst, CopyMakeBorderParam param, void* command_queue = NULL) = 0;
ArmMatConverterAcc
public:
virtual Status Copy(Mat& src, Mat& dst, void* command_queue = NULL);
virtual Status Resize(Mat& src, Mat& dst, ResizeParam param, void* command_queue = NULL);
virtual Status Crop(Mat& src, Mat& dst, CropParam param, void* command_queue = NULL);
virtual Status WarpAffine(Mat& src, Mat& dst, WarpAffineParam param, void* command_queue = NULL);
virtual Status CvtColor(Mat& src, Mat& dst, ColorConversionType type, void* command_queue = NULL);
virtual Status CopyMakeBorder(Mat& src, Mat& dst, CopyMakeBorderParam param, void* command_queue = NULL);
ArmMatConverterAcc::CvtColor
到此处时command_queue
没有用到。
CheckMatConverterParams 检查输入输出数据是否为空,以及是否在同一设备上。
NV21ToBGR
Status ret = TNN_OK;
ret = CheckMatConverterParams(src, dst, true);
if (ret != TNN_OK)
return ret;
switch (type) {
case COLOR_CONVERT_NV12TOBGR:
NV12ToBGR((uint8_t*)src.GetData(), (uint8_t*)dst.GetData(), src.GetBatch()*src.GetHeight(), src.GetWidth());
break;
case COLOR_CONVERT_NV21TOBGR:
NV21ToBGR((uint8_t*)src.GetData(), (uint8_t*)dst.GetData(), src.GetBatch()*src.GetHeight(), src.GetWidth());
break;
case COLOR_CONVERT_NV12TOBGRA:
NV12ToBGRA((uint8_t*)src.GetData(), (uint8_t*)dst.GetData(), src.GetBatch()*src.GetHeight(), src.GetWidth());
break;
case COLOR_CONVERT_NV21TOBGRA:
NV21ToBGRA((uint8_t*)src.GetData(), (uint8_t*)dst.GetData(), src.GetBatch()*src.GetHeight(), src.GetWidth());
break;
case COLOR_CONVERT_BGRTOGRAY:
BGRToGray((uint8_t*)src.GetData(), (uint8_t*)dst.GetData(), src.GetBatch()*src.GetHeight(), src.GetWidth());
break;
case COLOR_CONVERT_BGRATOGRAY:
BGRAToGray((uint8_t*)src.GetData(), (uint8_t*)dst.GetData(), src.GetBatch()*src.GetHeight(), src.GetWidth());
break;
case COLOR_CONVERT_RGBTOGRAY:
RGBToGray((uint8_t*)src.GetData(), (uint8_t*)dst.GetData(), src.GetBatch()*src.GetHeight(), src.GetWidth());
break;
case COLOR_CONVERT_RGBATOGRAY:
RGBAToGray((uint8_t*)src.GetData(), (uint8_t*)dst.GetData(), src.GetBatch()*src.GetHeight(), src.GetWidth());
break;
default:
return Status(TNNERR_PARAM_ERR, "ArmMatConverterAcc::CvtColor, color conversion type not support yet");
}
return ret;
NV21ToBGR
Float4
Float4::save 保存数据到指定地址。
Float4::max 两个对象中对应元素比较,取最大。
NV21ToBGR 每次处理8个像素。
NaiveYUVToBGROrBGRALoop
B = 1.164 ( Y − 16 ) + 2.018 ( U − 128 ) G = 1.164 ( Y − 16 ) − 0.813 ( V − 128 ) − 0.391 ( U − 128 ) R = 1.164 ( Y − 16 ) + 1.596 ( V − 128 ) begin{aligned} B &= 1.164(Y - 16) + 2.018(U - 128)\ G &= 1.164(Y - 16) - 0.813(V - 128) - 0.391(U - 128)\ R &= 1.164(Y - 16) + 1.596(V - 128) end{aligned} BGR=1.164(Y−16)+2.018(U−128)=1.164(Y−16)−0.813(V−128)−0.391(U−128)=1.164(Y−16)+1.596(V−128)
将系数左移8位运算,结果右移。
nn
为在每行中一次处理的像素数量。
#ifndef TNN_USE_NEON
return NaiveYUVToBGROrBGRA(nv21, bgr, 3, h, w, false);
#else
const unsigned char* yptr = nv21;
const unsigned char* vuptr = nv21 + w * h;
for (int y = 0; y < h; y += 2) {
const unsigned char* yptr0 = yptr;
const unsigned char* yptr1 = yptr + w;
unsigned char* rgb0 = bgr;
unsigned char* rgb1 = bgr + w * 3;
#if __aarch64__
int64_t nn = w >> 3;
int remain = w - (nn << 3);
int16x8_t _q1135 = vdupq_n_s16(1135);
int8x8_t _v74 = vdup_n_s8(74);
int8x8_t _v128 = vdup_n_s8(int8_t(128));
int8x8_t _v102 = vdup_n_s8(102);
int8x8_t _v52 = vdup_n_s8(52);
int8x8_t _v25 = vdup_n_s8(25);
// use 127 instead of 129 to prevent char overflow, add another 2 in asm
int8x8_t _v127 = vdup_n_s8(127);
// saturate uv to 240 to avoid b overflow
uint8x8_t _v240 = vdup_n_u8(240);
v0.8b-v2.8b
用于加载 YUV。
PRFM (literal) 为内存预取指令(prefetch memory)。
LD1 (vector, multiple structures) 将多个单元素结构加载到一个,两个,三个或四个寄存器。
V0-V31 寄存器中的数据是打包的。
CMHI (vector, register) 比较无符号的高(向量),将目标 SIMD 和 FP 寄存器中的向量设置为1或0。
BSL 按位选择。当原始目标位为1时,该指令将目标 SIMD&FP 寄存器中的每个位设置为来自第一源 SIMD&FP 寄存器的对应位,否则设置为来自第二源 SIMD&FP 寄存器的对应位。
ld1
读取8个字节,即4对 vu 到 V2 寄存器。
cmhi
和bsl
中两个操作数互换位置,实现v12.8b
的数值均小于240。
SUB (vector) 该指令从第一源 SIMD 和 FP 寄存器中的对应向量元素中减去第二源 SIMD 和 FP 寄存器中的每个向量元素,将结果放入向量中,并将该向量写入目标 SIMD 和 FP 寄存器中。
v2.8b
中存储
V
−
128
V-128
V−128 和
U
−
128
U-128
U−128。
if (nn > 0) {
asm volatile(
"prfm pldl1strm, [%[_vu], #128] nt"
"ld1 {v2.8b}, [%[_vu]], #8 nt"
"cmhi v12.8b, v2.8b, %[_v240].8b nt"
"bsl v12.8b, %[_v240].8b, v2.8b nt"
"sub v2.8b, v12.8b, %[_v128].8b nt"
加载相邻行的
Y
Y
Y 到v0.8b
和v1.8b
寄存器。
UMULL, UMULL2 (vector) 64位无符号数乘法指令。该指令将两个源 SIMD 和 FP 寄存器的下半部分或上半部分中的相应向量元素相乘,将结果放入向量中,然后将向量写入目标 SIMD 和 FP 寄存器中。
v28.8h
中为第1行的
74
Y
−
1135
74Y-1135
74Y−1135
ORR (vector, register) 在两个源 SIMD 和 FP 寄存器之间执行按位或运算,并将结果写入目标 SIMD 和 FP 寄存器。
复制v2.8b
中的值到v3.8b
,得到两份
V
−
128
V-128
V−128 和
U
−
128
U-128
U−128。
v29.8h
中为第2行的
74
Y
−
1135
74Y-1135
74Y−1135。
复制v28.16b
中的值到v9.16b
,得到两份第1行的
74
Y
−
1135
74Y-1135
74Y−1135。
"0: nt"
"prfm pldl1strm, [%[_y0], #128] nt"
"ld1 {v0.8b}, [%[_y0]], #8 nt"
"prfm pldl1strm, [%[_y1], #128] nt"
"ld1 {v1.8b}, [%[_y1]], #8 nt"
"umull v28.8h, v0.8b, %[_v74].8b nt"
"sub v28.8h, v28.8h, %[_q1135].8h nt" // v28 -> b0
"orr v3.8b, v2.8b, v2.8b nt"
"umull v29.8h, v1.8b, %[_v74].8b nt"
"sub v29.8h, v29.8h, %[_q1135].8h nt" // v29 -> b1
"orr v9.16b, v28.16b, v28.16b nt" // v9 -> g0
TRN1 (vector) 和 TRN2 (vector) 为转置向量 Transpose vector(primary)。TRN1 (vector) 指令从两个源 SIMD 和 FP 寄存器(从零开始)读取相应的偶数个向量元素,来自第一源寄存器的矢量元素被放置到目标矢量的偶数元素中,而来自第二源寄存器的矢量元素被放置到目标矢量的奇数元素中。
原本,v2.8b
和v3.8b
中的内容是相同的。转置后v30.8b
存储重复两次的
U
−
128
U-128
U−128,而v31.8b
存储重复的
V
−
128
V-128
V−128,一个元素当两个用。
复制v29.16b
的值到v11.16b
,得到两份第2行的
74
Y
−
1135
74Y-1135
74Y−1135。
SSHLL, SSHLL2 (vector) 为有符号长左移位(立即),Signed Shift Left Long (immediate)。此指令从源 SIMD 和 FP 寄存器中读取每个向量元素,按指定的移位量左移每个向量元素,将结果放入向量中,然后将向量写入目标 SIMD 和 FP 寄存器。目标向量元素的长度是源向量元素的两倍。SSHLL 指令从源寄存器的下半部分提取向量元素,而 SSHLL2 指令从源寄存器的上半部分提取向量元素。
v27.8h
存储
2
(
V
−
128
)
2(V-128)
2(V−128)。
SMLSL, SMLSL2 (vector) 有符号长乘减(向量)。该指令将两个源 SIMD 和 FP 寄存器的向量的下半部分或上半部分的对应有符号整数值相乘,并从目标 SIMD 和 FP 寄存器的向量元素中减去结果。目标向量元素的长度是被乘元素的两倍。SMLSL 指令从每个源寄存器的下半部分提取每个源向量,而 SMLSL2 指令从每个源寄存器的上半部分提取每个源向量。
v9.8h
中为第1行的
74
Y
−
1135
−
52
(
U
−
128
)
74Y-1135-52(U-128)
74Y−1135−52(U−128)
复制v28.16b
的值到v8.16b
,又得到一份第1行的
74
Y
−
1135
74Y-1135
74Y−1135。
复制v29.16b
的值到v10.16b
又得到一份第2行的
74
Y
−
1135
74Y-1135
74Y−1135。
v11.8h
中为第2行的
74
Y
−
1135
−
52
(
U
−
128
)
74Y-1135-52(U-128)
74Y−1135−52(U−128)
SMLAL, SMLAL2 (vector) 带符号的长乘加(向量)。该指令将两个源 SIMD 和 FP 寄存器的向量的下半部分或上半部分的对应有符号整数值相乘,并将结果与目标 SIMD 和 FP 寄存器的向量元素进行累加。目标向量元素的长度是被乘元素的两倍。SMLAL 指令从每个源寄存器的下半部分提取每个源向量,而 SMLAL2 指令从每个源寄存器的上半部分提取每个源向量。
v8.8h
为第1行的
74
Y
−
1135
+
102
(
U
−
128
)
74Y-1135 + 102(U-128)
74Y−1135+102(U−128)
v28.8h
为第1行的
74
Y
−
1135
+
127
(
V
−
128
)
74Y-1135 + 127(V-128)
74Y−1135+127(V−128)
v10.8h
为第2行的
74
Y
−
1135
+
102
(
U
−
128
)
74Y-1135 + 102(U-128)
74Y−1135+102(U−128)
"trn1 v30.8b, v2.8b, v3.8b nt" // u
"trn2 v31.8b, v2.8b, v3.8b nt" // v
"orr v11.16b, v29.16b, v29.16b nt" // v11 -> g1
"sshll v27.8h, v31.8b, #1 nt"
"smlsl v9.8h, v30.8b, %[_v52].8b nt"
"orr v8.16b, v28.16b, v28.16b nt" // v8 -> r0
"smlsl v11.8h, v30.8b, %[_v52].8b nt"
"orr v10.16b, v29.16b, v29.16b nt" // v10 -> r1
"smlal v8.8h, v30.8b, %[_v102].8b nt"
"smlal v28.8h, v31.8b, %[_v127].8b nt"
"smlal v10.8h, v30.8b, %[_v102].8b nt"
ADD (vector)
v28.8h
为第1行的
74
Y
−
1135
+
127
(
V
−
128
)
+
2
(
V
−
128
)
74Y-1135 + 127(V-128) + 2(V-128)
74Y−1135+127(V−128)+2(V−128)
v9.8h
中为第1行的
74
Y
−
1135
−
52
(
U
−
128
)
−
25
(
V
−
128
)
74Y-1135-52(U-128) - 25(V-128)
74Y−1135−52(U−128)−25(V−128)
v29.8h
中为第2行的
74
Y
−
1135
+
127
(
V
−
128
)
74Y-1135 + 127(V-128)
74Y−1135+127(V−128)
v11.8h
中为第2行的
74
Y
−
1135
−
52
(
U
−
128
)
−
25
(
V
−
128
)
74Y-1135-52(U-128)- 25(V-128)
74Y−1135−52(U−128)−25(V−128)
v29.8h
中为第2行的
74
Y
−
1135
+
127
(
V
−
128
)
+
2
(
V
−
128
)
74Y-1135 + 127(V-128) + 2(V-128)
74Y−1135+127(V−128)+2(V−128)
"add v28.8h, v28.8h, v27.8h nt"
"smlsl v9.8h, v31.8b, %[_v25].8b nt"
"smlal v29.8h, v31.8b, %[_v127].8b nt"
"smlsl v11.8h, v31.8b, %[_v25].8b nt"
"add v29.8h, v29.8h, v27.8h nt"
v29.8h
是第2行的
74
Y
−
1135
+
2
(
V
−
128
)
74Y-1135 + 2(V-128)
74Y−1135+2(V−128)。
SQSHRUN, SQSHRUN2 (vector) 有符号饱和右移无符号缩小(立即)。此指令读取源 SIMD 和 FP 寄存器向量中的每个有符号整数值,将每个值右移一个立即数,将结果饱和为原始宽度的一半的无符号整数值,将最终结果放入向量,并将向量写入目标SIMD和FP寄存器。
v26.8b
中为第1行的
R
=
74
Y
−
1135
+
102
(
U
−
128
)
2
6
R = frac{74Y-1135 +102(U-128)}{2^6}
R=2674Y−1135+102(U−128)
v24.8b
中为第1行的
B
=
74
Y
−
1135
+
129
(
V
−
128
)
2
6
B = frac{74Y-1135 +129(V-128)}{2^6}
B=2674Y−1135+129(V−128)
v6.8b
中为第2行的
R
=
74
Y
−
1135
−
102
(
U
−
128
)
2
6
R = frac{74Y-1135 -102(U-128)}{2^6}
R=2674Y−1135−102(U−128)
v25.8b
中为第1行的
G
=
74
Y
−
1135
−
52
(
U
−
128
)
−
25
(
V
−
128
)
2
6
G = frac{74Y-1135-52(U-128)- 25(V-128)}{2^6}
G=2674Y−1135−52(U−128)−25(V−128)
v4.8b
中为第2行的
B
=
74
Y
−
1135
+
129
(
V
−
128
)
2
6
B = frac{74Y-1135 + 129(V-128)}{2^6}
B=2674Y−1135+129(V−128)
v5.8b
中为第2行的
G
=
74
Y
−
1135
−
52
(
U
−
128
)
−
25
(
V
−
128
)
2
6
G = frac{74Y-1135-52(U-128)- 25(V-128)}{2^6}
G=2674Y−1135−52(U−128)−25(V−128)
"sqshrun v26.8b, v8.8h, #6 nt" // v24-v26: b0g0r0
"sqshrun v24.8b, v28.8h, #6 nt"
"sqshrun v6.8b, v10.8h, #6 nt"
"sqshrun v25.8b, v9.8h, #6 nt" // v4-v6: b1g1r1
"sqshrun v4.8b, v29.8h, #6 nt"
"sqshrun v5.8b, v11.8h, #6 nt"
为何再次读取 UV 值?
SUBS (immediate) 减法(立即数),设置标志,从寄存器值中减去可选移位的立即数,然后将结果写入目标寄存器。 它根据结果更新条件标志。
ST3 (vector, multiple structures) 通过三个寄存器存储多个3元素结构。该指令从三个 SIMD 和 FP 寄存器将多个3元素结构交错存储到内存中。
v24.8b-v26.8b
为第一行的像素,v4.8b-v6.8b
为第二行的像素。
beq
检查 Z 标志位,若未置位则跳转到标号0
处循环处理。
最后为何%[_vu]
减8?
"prfm pldl1strm, [%[_vu], #128] nt"
"ld1 {v2.8b}, [%[_vu]], #8 nt"
"subs %[_nn], %[_nn], #1 nt"
"prfm pstl1strm, [%[_r0]] nt"
"st3 {v24.8b-v26.8b}, [%[_r0]], #24 nt"
"cmhi v12.8b, v2.8b, %[_v240].8b nt"
"bsl v12.8b, %[_v240].8b, v2.8b nt"
"sub v2.8b, v12.8b, %[_v128].8b nt"
"prfm pstl1strm, [%[_r1]] nt"
"st3 {v4.8b-v6.8b}, [%[_r1]], #24 nt"
"bne 0b nt"
"sub %[_vu], %[_vu], #8 nt"
: [_nn]"+r"(nn),
[_y0]"+r"(yptr0),
[_y1]"+r"(yptr1),
[_vu]"+r"(vuptr),
[_r0]"+r"(rgb0),
[_r1]"+r"(rgb1)
: [_v128]"w"(_v128),
[_v102]"w"(_v102),
[_v52]"w"(_v52),
[_v25]"w"(_v25),
[_v127]"w"(_v127),
[_q1135]"w"(_q1135),
[_v74]"w"(_v74),
[_v240]"w"(_v240)
: "cc", "memory", "x0", "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v8",
"v9", "v10", "v11", "v12", "v24", "v25", "v26","v27", "v28", "v29", "v30", "v31"
);
}
armv7a 的计算。
#else
int nn = w >> 3;
int remain = w - (nn << 3);
short _s1135 = 1135;
int8x8_t _v74 = vdup_n_s8(74);
int8x8_t _v128 = vdup_n_s8(int8_t(128));
// to much input w cause compile error, merge to one
int8x8_t _vuvfilter = {102, 52, 25, 127, 0, 0, 0, 0};
// saturate uv to 240 to avoid b overflow
uint8x8_t _v240 = vdup_n_u8(240);
if (nn > 0) {
asm volatile(
"pld [%[_vu], #128] n"
"vld1.u8 {d2}, [%[_vu]]! n"
"vcgt.u8 d27, d2, %[_v240] n"
"vbsl.u8 d27, %[_v240], d2 n"
"vsub.u8 d2, d27, %[_v128] n"
"vmov.s8 d10, %[_filt] n"
"vdup.8 d11, d10[1] n" // v52
"vdup.8 d12, d10[2] n" // v25
"vdup.8 d13, d10[3] n" // v127
"vdup.16 q7, %[_s1135] n" // q1135
"vdup.8 d10, d10[0] n" // v102
"0: n"
"pld [%[_y0], #128] n"
"vld1.u8 {d0}, [%[_y0]]! n"
"pld [%[_y1], #128] n"
"vld1.u8 {d1}, [%[_y1]]! n"
"vmull.u8 q2, d0, %[_v74] n"
"vorr d3, d2, d2 n"
"vsub.s16 q2, q2, q7 n" // q2 -> b0
"vmull.u8 q3, d1, %[_v74] n"
"vorr q9, q2, q2 n" // q9 -> g0
"vsub.s16 q3, q3, q7 n" // q3 -> b1
"vtrn.s8 d2, d3 n" // d2 -> u, d3 -> v
"vorr q11, q3, q3 n" // q11 -> g1
"vshll.s8 q4, d3, #1 n"
"vmlsl.s8 q9, d2, d11 n"
"vorr q8, q2, q2 n" // q8 -> r0
"vmlsl.s8 q11, d2, d11 n"
"vorr q10, q3, q3 n" // q10 -> r1
"vmlal.s8 q8, d2, d10 n"
"vmlal.s8 q2, d3, d13 n"
"vmlal.s8 q10, d2, d10 n"
"vadd.s16 q2, q2, q4 n"
"vmlsl.s8 q9, d3, d12 n"
"vmlal.s8 q3, d3, d13 n"
"vmlsl.s8 q11,d3, d12 n"
"vadd.s16 q3, q3, q4 n"
"vqshrun.s16 d26, q8, #6 n" // d24-d26: b0g0r0
"vqshrun.s16 d24, q2, #6 n"
"vqshrun.s16 d4, q3, #6 n"
"vqshrun.s16 d25, q9, #6 n" // d4-d6: b1g1r1
"vqshrun.s16 d6, q10, #6 n"
"vqshrun.s16 d5, q11, #6 n"
"pld [%[_vu], #128] n"
"vld1.u8 {d2}, [%[_vu]]! n"
"subs %[_nn], #1 n"
"vst3.u8 {d24-d26}, [%[_r0]]!n"
"vcgt.u8 d27, d2, %[_v240] n"
"vbsl.u8 d27, %[_v240], d2 n"
"vsub.u8 d2, d27, %[_v128] n"
"vst3.u8 {d4-d6}, [%[_r1]]!n"
"bne 0b n"
"sub %[_vu], #8 n"
: [_nn]"+r"(nn),
[_y0]"+r"(yptr0),
[_y1]"+r"(yptr1),
[_vu]"+r"(vuptr),
[_r0]"+r"(rgb0),
[_r1]"+r"(rgb1)
: [_v128]"w"(_v128),
[_filt]"w"(_vuvfilter),
[_v74]"w"(_v74),
[_s1135]"r"(_s1135),
[_v240]"w"(_v240)
: "cc", "memory", "q0", "q1", "q2", "q3","q4","q5","q6","q7","q8", "q9", "q10", "q11", "q12", "q13"
);
}
#endif //__aarch64__
NaiveYUVToBGROrBGRALoop(yptr0, yptr1, vuptr, rgb0, rgb1, remain, false, 3);
yptr += 2*w;
vuptr += remain;
bgr += 2*3*w;
}
#endif // TNN_USE_NEON
NaiveYUVToBGROrBGRA
NaiveYUVToBGROrBGRALoop 每次处理两行。
const unsigned char* yptr = yuv;
const unsigned char* vuptr = yuv + w * h;
for (int y = 0; y < h; y += 2) {
const unsigned char* yptr0 = yptr;
const unsigned char* yptr1 = yptr + w;
unsigned char* rgb0 = bgr;
unsigned char* rgb1 = bgr + w * channel;
NaiveYUVToBGROrBGRALoop(yptr0, yptr1, vuptr, rgb0, rgb1, w, is_nv12, channel);
yptr += 2*w;
vuptr += w;
bgr += 2*channel*w;
}
NaiveYUVToBGROrBGRALoop
循环内每次处理两个像素。
u
和v
像素值若超过240则截断。
[
R
G
B
]
=
[
1.164
0.000
1.596
1.164
−
0.392
−
0.813
1.164
2.017
0.000
]
[
(
Y
−
16
)
(
C
b
−
128
)
(
C
r
−
128
)
]
=
1
2
6
[
74
0
102
74
−
25
−
52
74
129
0
]
[
(
Y
−
16
)
(
C
b
−
128
)
(
C
r
−
128
)
]
begin{bmatrix} R \ G \ B end{bmatrix}= begin{bmatrix} 1.164 & 0.000 & 1.596 \ 1.164 & -0.392 & -0.813 \ 1.164 & 2.017 & 0.000 end{bmatrix} begin{bmatrix} (Y-16) \ (Cb-128)\ (Cr-128) end{bmatrix}= frac{1}{2^6}begin{bmatrix} 74 & 0 & 102 \ 74 & -25 & -52 \ 74 & 129 & 0 end{bmatrix} begin{bmatrix} (Y-16) \ (Cb-128)\ (Cr-128) end{bmatrix}
RGB
=
1.1641.1641.1640.000−0.3922.0171.596−0.8130.000
(Y−16)(Cb−128)(Cr−128)
=261
7474740−25129102−520
(Y−16)(Cb−128)(Cr−128)
SATURATE_CAST_UCHAR
用于溢出处理。
for (; remain > 0; remain -= 2) {
int u, v;
if (is_nv12) {
u = (vuptr[0] > 240 ? 240 : vuptr[0]) - 128;
v = (vuptr[1] > 240 ? 240 : vuptr[1]) - 128;
} else {
v = (vuptr[0] > 240 ? 240 : vuptr[0]) - 128;
u = (vuptr[1] > 240 ? 240 : vuptr[1]) - 128;
}
int ruv = 102 * v;
int guv = -52 * v + -25 * u;
int buv = 129 * u;
#define SATURATE_CAST_UCHAR(X) (unsigned char)std::min(std::max(X, 0), 255);
int y00 = yptr0[0]* 74 - 1135;
if (channel == 4)
rgb0[3] = 255;
rgb0[0 * channel + 2] = SATURATE_CAST_UCHAR((y00 + ruv) >> 6);
rgb0[0 * channel + 1] = SATURATE_CAST_UCHAR((y00 + guv) >> 6);
rgb0[0 * channel + 0] = SATURATE_CAST_UCHAR((y00 + buv) >> 6);
int y01 = yptr0[1]* 74 - 1135;
if (channel == 4)
rgb0[7] = 255;
rgb0[1 * channel + 2] = SATURATE_CAST_UCHAR((y01 + ruv) >> 6);
rgb0[1 * channel + 1] = SATURATE_CAST_UCHAR((y01 + guv) >> 6);
rgb0[1 * channel + 0] = SATURATE_CAST_UCHAR((y01 + buv) >> 6);
int y10 = yptr1[0]* 74 - 1135;
if (channel == 4)
rgb1[3] = 255;
rgb1[0 * channel + 2] = SATURATE_CAST_UCHAR((y10 + ruv) >> 6);
rgb1[0 * channel + 1] = SATURATE_CAST_UCHAR((y10 + guv) >> 6);
rgb1[0 * channel + 0] = SATURATE_CAST_UCHAR((y10 + buv) >> 6);
int y11 = yptr1[1]* 74 - 1135;
if (channel == 4)
rgb1[7] = 255;
rgb1[1 * channel + 2] = SATURATE_CAST_UCHAR((y11 + ruv) >> 6);
rgb1[1 * channel + 1] = SATURATE_CAST_UCHAR((y11 + guv) >> 6);
rgb1[1 * channel + 0] = SATURATE_CAST_UCHAR((y11 + buv) >> 6);
#undef SATURATE_CAST_UCHAR
yptr0 += 2;
yptr1 += 2;
vuptr += 2;
rgb0 += 2*channel;
rgb1 += 2*channel;
}
参考资料:
- C++ Tutorial: Auto Registering Factory
- Automatic object factory in C++
- Unforgettable Factory Registration
- Factory Method design patter with self registering derived classes
- Factory Method in C++
- 原型模式
- Factory method pattern
- 详解设计模式 | 抽象工厂
- 4. 建造者模式
- 30.1 工厂方法模式VS建造者模式
- 建造者模式
- 建造者模式(Builder Pattern)- 最易懂的设计模式解析
- Chapter 4 建造者模式(Builder Pattern)
- 人人都会设计模式—建造者模式–Builder
- C++11于once_flag,call_once分析的实现
- C++中once_flag、call_once使用
- Optimize RGBA->RGB arm64 assembly
- preload-practice.zh
- 飞腾CPU体系结构(十一)
- aarch64 neon指令集拾遗
- ARM架构64位入门基础:架构分析、寄存器、调用规则、指令集、程序调试以及参考手册
- ARM NEON编程初探——一个简单的BGR888转YUV444实例详解
- YUV 格式与 RGB 格式的相互转换公式及C++ 代码
- 【arm】arm32位和arm64位架构、寄存器和指令差异分析总结
- Armv8 指令集
- AN12628 Optimizing Memory Copy Routines
- ARM Neon 常用指令
- ARM NEON SIMD 指令优化
- Dealing with the ARM AArch64 SIMD documentation
- Introduction to ARM64 NEON assembly
- ARMv8常用指令
- AI 移动端框架常用指令·汇总(待续)
- What kind of assembly instruction is this ld1 {v0.16b}, %[in]?
- chromium/external/libyuv/master/./source/row_neon64.cc
- Arm NEON programming quick reference
- AI移动端常用汇编指令汇总以及底层算子汇编实现(附带一点点干货)
- ARMv8 Neon Programming
- ARM_NEON_CNN编程
- 6.47.2 Extended Asm - Assembler Instructions with C Expression Operands
- How to Use Inline Assembly Language in C Code(C语言内联汇编)–continuing…
- TRN1
- ARM64 汇编指令总结
- 浅谈移动工程师跨界机器学习之路
- ARM指令集之乘法指令
- What kind of assembly instruction is this ld1 {v0.16b}, %[in]?
- ARM Instruction Set
- Lecture 8: ARM Arithmetic and BitweiseInstructions
- Arm A64 Instruction Set Architecture
- arm CPU 2D卷积计算方法一览
- ARM Assembly Programming
- 关于ARM中的tst、cmp、bne、beq指令
- Introducing ARM assembly language
- ARM Data Types and Registers (Part 2) | Azeria Labs
- First look at the arm64 architecture and assembly language
- gemmlowp/internal/kernel_neon.h
- ARMv8-A A64 ISA Overview
- What is the fastest way to index into ARMv8 registers
- 移动端arm cpu优化学习笔记第4弹–内联汇编入门
- 【Arm端算法优化笔记】一,一步步优化盒子滤波算法
- ARMv8 中的 SIMD 运算
- 用NEON intrinsic实现RGB转YUV420SP(NV12)
- YUV转RGB(NV21-ARGB)的Neon优化代码
- 运用NEON指令集加速RGB与YUV相互转换
- What is arrangement specifier(.16b,.8b) in ARM assembly language instructions?
- TNN新版本上线!全新特性,更加好用!
最后
以上就是玩命裙子为你收集整理的TNN MatConverter CvtColor NV21TOBGR的全部内容,希望文章能够帮你解决TNN MatConverter CvtColor NV21TOBGR所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复