如标题所示,我有一个 Raspberry Pi 5(Armv8 的 Cortex-A76;四核),我使用 OpenCV 在上面做一些事情。
我使用cv::cvtColor获取RGB2Gray,在Raspberry Pi 5上速度很慢,所以我使用neon_arm.h来优化速度,确实更快了。
在版本 1 中,我使用 vld3_u8(),它每次从图像加载 uint8x8x3_t 数据(64 位寄存器)。
在 Version-2 I 中,使用 vld3q_u8() ,每次从图像中加载 uint8x16x3_t 数据(128 位)。
我使用 std::chrono 对代码进行计时;奇怪的是,版本 1 比版本 2 更快(在我看来应该更慢)。
版本1:10.5ms(100次平均)。
版本2:12.7ms(100次平均)。
照片尺寸为3009x4248。
我不知道是否有什么问题(可能是设备或代码?)。
谁能教我为什么?
我的代码如下所示。
void neon_cv_8(cv::Mat &src,cv::Mat &grey,std::size_t size)
{
uint8_t *p_img = src.data;
uint8_t *p_o = grey.data;
uint8x8_t r_corr = vdup_n_u8(76);
uint8x8_t g_corr = vdup_n_u8(150);
uint8x8_t b_corr = vdup_n_u8(30);
for(std::size_t i = 0; i<size ; i+=8)
{
uint8x8x3_t vdata = vld3_u8(p_img);
uint16x8_t tmp = vmull_u8(b_corr, vdata.val[0]);
tmp = vmlal_u8(tmp, g_corr, vdata.val[1]);
tmp = vmlal_u8(tmp, r_corr, vdata.val[2]);
uint8x8_t vgray = vshrn_n_u16(tmp, 8);
vst1_u8(p_o, vgray);
p_o += 8;
p_img += 8 * 3;
}
}//version-1
void neon_cv_16(cv::Mat &src,cv::Mat &grey,std::size_t size)
{
uint8_t *p_img = src.data;
uint8_t *p_o = grey.data;
uint16x4_t r_corr = vdup_n_u16(76);
uint16x4_t g_corr = vdup_n_u16(150);
uint16x4_t b_corr = vdup_n_u16(30);
for(std::size_t i = 0; i<size ; i+=2*8)
{
uint8x16x3_t vdata = vld3q_u8(p_img);
uint16x8_t v_b = vmovl_u8(vget_low_u8(vdata.val[0])),
v_g = vmovl_u8(vget_low_u8(vdata.val[1])),
v_r = vmovl_u8(vget_low_u8(vdata.val[2]));
uint32x4_t tmp_low = vmull_u16(b_corr, vget_low_u16(v_b));
uint32x4_t tmp_high = vmull_u16(b_corr,vget_high_u16(v_b));
tmp_low = vmlal_u16(tmp_low,g_corr, vget_low_u16(v_g));
tmp_high = vmlal_u16(tmp_high,g_corr,vget_high_u16(v_g));
tmp_low = vmlal_u16(tmp_low,r_corr, vget_low_u16(v_r));
tmp_high = vmlal_u16(tmp_high,r_corr,vget_high_u16(v_r));
uint8x8_t vgray0 = vqmovn_u16(vcombine_u16(vrshrn_n_u32(tmp_low,8)
,vrshrn_n_u32(tmp_high,8)));
v_r = vmovl_u8(vget_high_u8(vdata.val[0])),
v_g = vmovl_u8(vget_high_u8(vdata.val[1])),
v_b = vmovl_u8(vget_high_u8(vdata.val[2]));
tmp_low = vmull_u16(b_corr, vget_low_u16(v_b));
tmp_high = vmull_u16(b_corr,vget_high_u16(v_b));
tmp_low = vmlal_u16(tmp_low,g_corr, vget_low_u16(v_g));
tmp_high = vmlal_u16(tmp_high,g_corr,vget_high_u16(v_g));
tmp_low = vmlal_u16(tmp_low,r_corr, vget_low_u16(v_r));
tmp_high = vmlal_u16(tmp_high,r_corr,vget_high_u16(v_r));
uint8x8_t vgray1 = vqmovn_u16(vcombine_u16(vrshrn_n_u32(tmp_low,8)
,vrshrn_n_u32(tmp_high,8)));
vst1q_u8(p_o, vcombine_u8(vgray0,vgray1));
p_o += 16;
p_img += 48;
}
}//version-2
与旧的 32 位 ARMv7 不同,ARM64 SIMD 为 16 字节宽。像 vget_high_something 这样的内在函数不再是免费的;它们编译成随机播放指令。不过,vget_low_something 仍然是免费的。
您应该使用
vmull_high_u16
和 vmlal_high_u16
对向量的上半部分进行数学运算,而无需进行洗牌。同样,vqmovn_high_u16
将结果减少为字节。