TNN MatConverter CvtColor NV21TOBGR

news/2024/7/21 7:25:44 标签: 深度学习, TNN, 图像处理, arm, asm

OpenCV 中的 carotene 对于 armv7优化较好,而 armv8下则是 NEON 实现。TNN 提供了一套图像预处理接口并且进行了汇编优化。下面以 NV21TOBGR 为例进行介绍。

TNN/blob/master/include/tnn/utils/mat_utils.h#L74">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);

TNN/blob/master/source/tnn/utils/mat_utils.cc#L171">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);

TNN/blob/master/source/tnn/utils/mat_utils.cc#L38">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;

TNN/blob/master/source/tnn/utils/mat_utils.cc#L22">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");  \
    }

TNN/blob/master/source/tnn/utils/mat_converter_acc.h#L49">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_;

TNN/blob/master/source/tnn/utils/mat_converter_acc.cc#L23">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;

TNN/blob/master/source/tnn/utils/mat_converter_acc.cc#L30">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;

TNN/blob/master/source/tnn/utils/mat_converter_acc.cc#L38">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 twice\n", type);
        return 1;
    }
    if (!creater) {
        LOGE("Error: MatConverterAccCreater is nil device_type(%d)\n", type);
        return 1;
    }
    converter_creater_map_[type] = creater;
    return 0;

TNN/blob/master/source/tnn/utils/mat_converter_acc.h#L43">MatConverterAccCreater

为工厂方法。

public:
    virtual ~MatConverterAccCreater(){};
    virtual std::shared_ptr<MatConverterAcc> CreateMatConverterAcc() = 0;

TNN/blob/master/source/tnn/utils/mat_converter_acc.h#L73">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>();                                                        \
        };                                                                                                             \
    }

TNN/blob/master/source/tnn/utils/mat_converter_acc.h#L82">REGISTER_MAT_CONVERTER

以定义变量的形式,向全局字典中注册特定设备的矩阵转换器。

#define REGISTER_MAT_CONVERTER(device, device_type)                                                                    \
    MatConverterAccRegister<device##MatConverterAccCreater> g_mat_converter_##device(device_type)

TNN/blob/master/source/tnn/utils/mat_converter_acc.h#L29">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;

TNN/blob/master/source/tnn/device/arm/arm_mat_converter.h#L24">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);

TNN/blob/master/source/tnn/device/arm/arm_mat_converter.cc#L205">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;

TNN/blob/master/source/tnn/device/arm/arm_util.cc#L971">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(Y16)+2.018(U128)=1.164(Y16)0.813(V128)0.391(U128)=1.164(Y16)+1.596(V128)

将系数左移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 寄存器。
cmhibsl中两个操作数互换位置,实现v12.8b的数值均小于240。
SUB (vector) 该指令从第一源 SIMD 和 FP 寄存器中的对应向量元素中减去第二源 SIMD 和 FP 寄存器中的每个向量元素,将结果放入向量中,并将该向量写入目标 SIMD 和 FP 寄存器中。
v2.8b中存储 V − 128 V-128 V128 U − 128 U-128 U128

        if (nn > 0) {
            asm volatile(
                "prfm  pldl1strm, [%[_vu], #128]    \n\t"
                "ld1   {v2.8b},   [%[_vu]], #8      \n\t"
                "cmhi  v12.8b, v2.8b, %[_v240].8b   \n\t"
                "bsl   v12.8b, %[_v240].8b, v2.8b   \n\t"
                "sub   v2.8b, v12.8b, %[_v128].8b   \n\t"

加载相邻行的 Y Y Yv0.8bv1.8b寄存器。
UMULL, UMULL2 (vector) 64位无符号数乘法指令。该指令将两个源 SIMD 和 FP 寄存器的下半部分或上半部分中的相应向量元素相乘,将结果放入向量中,然后将向量写入目标 SIMD 和 FP 寄存器中。
v28.8h中为第1行的 74 Y − 1135 74Y-1135 74Y1135
ORR (vector, register) 在两个源 SIMD 和 FP 寄存器之间执行按位或运算,并将结果写入目标 SIMD 和 FP 寄存器。
复制v2.8b中的值到v3.8b,得到两份 V − 128 V-128 V128 U − 128 U-128 U128
v29.8h中为第2行的 74 Y − 1135 74Y-1135 74Y1135
复制v28.16b中的值到v9.16b,得到两份第1行的 74 Y − 1135 74Y-1135 74Y1135

                "0:                                 \n\t"
                "prfm  pldl1strm, [%[_y0], #128]    \n\t"
                "ld1   {v0.8b},   [%[_y0]], #8      \n\t"
                "prfm  pldl1strm, [%[_y1], #128]    \n\t"
                "ld1   {v1.8b},   [%[_y1]], #8      \n\t"
                "umull v28.8h, v0.8b,  %[_v74].8b   \n\t"
                "sub   v28.8h, v28.8h, %[_q1135].8h \n\t"   // v28 -> b0
                "orr   v3.8b,  v2.8b,  v2.8b        \n\t"
                "umull v29.8h, v1.8b,  %[_v74].8b   \n\t"
                "sub   v29.8h, v29.8h, %[_q1135].8h \n\t"   // v29 -> b1
                "orr   v9.16b, v28.16b, v28.16b     \n\t"   // v9  -> g0

TRN1 (vector) 和 TRN2 (vector) 为转置向量 Transpose vector(primary)。TRN1 (vector) 指令从两个源 SIMD 和 FP 寄存器(从零开始)读取相应的偶数个向量元素,来自第一源寄存器的矢量元素被放置到目标矢量的偶数元素中,而来自第二源寄存器的矢量元素被放置到目标矢量的奇数元素中。
原本,v2.8bv3.8b中的内容是相同的。转置后v30.8b存储重复两次的 U − 128 U-128 U128,而v31.8b存储重复的 V − 128 V-128 V128,一个元素当两个用。
复制v29.16b的值到v11.16b,得到两份第2行的 74 Y − 1135 74Y-1135 74Y1135
SSHLL, SSHLL2 (vector) 为有符号长左移位(立即),Signed Shift Left Long (immediate)。此指令从源 SIMD 和 FP 寄存器中读取每个向量元素,按指定的移位量左移每个向量元素,将结果放入向量中,然后将向量写入目标 SIMD 和 FP 寄存器。目标向量元素的长度是源向量元素的两倍。SSHLL 指令从源寄存器的下半部分提取向量元素,而 SSHLL2 指令从源寄存器的上半部分提取向量元素。
v27.8h存储 2 ( V − 128 ) 2(V-128) 2(V128)
SMLSL, SMLSL2 (vector) 有符号长乘减(向量)。该指令将两个源 SIMD 和 FP 寄存器的向量的下半部分或上半部分的对应有符号整数值相乘,并从目标 SIMD 和 FP 寄存器的向量元素中减去结果。目标向量元素的长度是被乘元素的两倍。SMLSL 指令从每个源寄存器的下半部分提取每个源向量,而 SMLSL2 指令从每个源寄存器的上半部分提取每个源向量。
v9.8h中为第1行的 74 Y − 1135 − 52 ( U − 128 ) 74Y-1135-52(U-128) 74Y113552(U128)
复制v28.16b的值到v8.16b,又得到一份第1行的 74 Y − 1135 74Y-1135 74Y1135
复制v29.16b的值到v10.16b又得到一份第2行的 74 Y − 1135 74Y-1135 74Y1135
v11.8h中为第2行的 74 Y − 1135 − 52 ( U − 128 ) 74Y-1135-52(U-128) 74Y113552(U128)
SMLAL, SMLAL2 (vector) 带符号的长乘加(向量)。该指令将两个源 SIMD 和 FP 寄存器的向量的下半部分或上半部分的对应有符号整数值相乘,并将结果与目标 SIMD 和 FP 寄存器的向量元素进行累加。目标向量元素的长度是被乘元素的两倍。SMLAL 指令从每个源寄存器的下半部分提取每个源向量,而 SMLAL2 指令从每个源寄存器的上半部分提取每个源向量。
v8.8h为第1行的 74 Y − 1135 + 102 ( U − 128 ) 74Y-1135 + 102(U-128) 74Y1135+102(U128)
v28.8h为第1行的 74 Y − 1135 + 127 ( V − 128 ) 74Y-1135 + 127(V-128) 74Y1135+127(V128)
v10.8h为第2行的 74 Y − 1135 + 102 ( U − 128 ) 74Y-1135 + 102(U-128) 74Y1135+102(U128)

                "trn1  v30.8b, v2.8b, v3.8b         \n\t"   // u
                "trn2  v31.8b, v2.8b, v3.8b         \n\t"   // v
                "orr   v11.16b, v29.16b, v29.16b    \n\t"   // v11 -> g1
                "sshll v27.8h, v31.8b, #1           \n\t"
                "smlsl v9.8h,  v30.8b, %[_v52].8b   \n\t"
                "orr   v8.16b, v28.16b, v28.16b     \n\t"   // v8  -> r0
                "smlsl v11.8h, v30.8b, %[_v52].8b   \n\t"
                "orr   v10.16b, v29.16b, v29.16b    \n\t"   // v10 -> r1
                "smlal v8.8h,  v30.8b, %[_v102].8b  \n\t"
                "smlal v28.8h, v31.8b, %[_v127].8b  \n\t"
                "smlal v10.8h, v30.8b, %[_v102].8b  \n\t"

ADD (vector)
v28.8h为第1行的 74 Y − 1135 + 127 ( V − 128 ) + 2 ( V − 128 ) 74Y-1135 + 127(V-128) + 2(V-128) 74Y1135+127(V128)+2(V128)
v9.8h中为第1行的 74 Y − 1135 − 52 ( U − 128 ) − 25 ( V − 128 ) 74Y-1135-52(U-128) - 25(V-128) 74Y113552(U128)25(V128)
v29.8h中为第2行的 74 Y − 1135 + 127 ( V − 128 ) 74Y-1135 + 127(V-128) 74Y1135+127(V128)
v11.8h中为第2行的 74 Y − 1135 − 52 ( U − 128 ) − 25 ( V − 128 ) 74Y-1135-52(U-128)- 25(V-128) 74Y113552(U128)25(V128)
v29.8h中为第2行的 74 Y − 1135 + 127 ( V − 128 ) + 2 ( V − 128 ) 74Y-1135 + 127(V-128) + 2(V-128) 74Y1135+127(V128)+2(V128)

                "add   v28.8h, v28.8h, v27.8h       \n\t"
                "smlsl v9.8h,  v31.8b, %[_v25].8b   \n\t"
                "smlal v29.8h, v31.8b, %[_v127].8b  \n\t"
                "smlsl v11.8h, v31.8b, %[_v25].8b   \n\t"
                "add   v29.8h, v29.8h, v27.8h       \n\t"

v29.8h是第2行的 74 Y − 1135 + 2 ( V − 128 ) 74Y-1135 + 2(V-128) 74Y1135+2(V128)
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=2674Y1135+102(U128)
v24.8b中为第1行的 B = 74 Y − 1135 + 129 ( V − 128 ) 2 6 B = \frac{74Y-1135 +129(V-128)}{2^6} B=2674Y1135+129(V128)
v6.8b中为第2行的 R = 74 Y − 1135 − 102 ( U − 128 ) 2 6 R = \frac{74Y-1135 -102(U-128)}{2^6} R=2674Y1135102(U128)
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=2674Y113552(U128)25(V128)
v4.8b 中为第2行的 B = 74 Y − 1135 + 129 ( V − 128 ) 2 6 B = \frac{74Y-1135 + 129(V-128)}{2^6} B=2674Y1135+129(V128)
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=2674Y113552(U128)25(V128)

                "sqshrun v26.8b, v8.8h,  #6         \n\t"   // v24-v26: b0g0r0
                "sqshrun v24.8b, v28.8h, #6         \n\t"
                "sqshrun v6.8b,  v10.8h, #6         \n\t"
                "sqshrun v25.8b, v9.8h,  #6         \n\t"   // v4-v6: b1g1r1
                "sqshrun v4.8b,  v29.8h, #6         \n\t"
                "sqshrun v5.8b,  v11.8h, #6         \n\t"

为何再次读取 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]     \n\t"
                "ld1 {v2.8b},    [%[_vu]], #8       \n\t"
                "subs %[_nn], %[_nn], #1            \n\t"
                "prfm pstl1strm, [%[_r0]]           \n\t"
                "st3 {v24.8b-v26.8b}, [%[_r0]], #24 \n\t"
                "cmhi  v12.8b, v2.8b, %[_v240].8b   \n\t"
                "bsl   v12.8b, %[_v240].8b, v2.8b   \n\t"
                "sub   v2.8b, v12.8b, %[_v128].8b   \n\t"
                "prfm pstl1strm, [%[_r1]]           \n\t"
                "st3 {v4.8b-v6.8b},   [%[_r1]], #24 \n\t"
                "bne 0b                             \n\t"
                "sub %[_vu], %[_vu], #8             \n\t"

                : [_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

TNN/blob/master/source/tnn/utils/naive_compute.cc#L725">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;
    }

TNN/blob/master/source/tnn/utils/naive_compute.cc#L669">NaiveYUVToBGROrBGRALoop

循环内每次处理两个像素。
uv像素值若超过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.0000.3922.0171.5960.8130.000(Y16)(Cb128)(Cr128)=261747474025129102520(Y16)(Cb128)(Cr128)

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++ 代码
  • armarm32位和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新版本上线!全新特性,更加好用!

http://www.niftyadmin.cn/n/892705.html

相关文章

Flatbuffers 的 C++使用示例

monster.fbs FlatBuffers 使用.来指定嵌套的名称空间/包。 root_type声明序列化数据的根表&#xff08;或结构&#xff09;。 除了基本类型外&#xff0c;Monster中包含一个表和一个结构体。 表是在 FlatBuffers 中定义对象的主要方式&#xff0c;由名称&#xff08;此处为Mo…

CMake 编译 rkmedia

在瑞芯微的使用手册中&#xff0c;rkmedia 库使用 Buildroot 编译。然而由于配置文件众多&#xff0c;不易定位编译过程中的问题&#xff0c;所以本文以 CMake 进行构建。目标平台为 RV1109/1126。 编译 libdrm-rockchip rkmedia 中的组件支持选项配置&#xff0c;但 drm 是必…

OpenCV 中的 warpAffine

warpAffine 是图像处理中比较常见的一种变换&#xff0c;可以将图像校正或对齐。 对于线性插值方式&#xff0c;OpenCV 首先将坐标映射保存成两张图&#xff0c;然后调用 remap 函数。第二步是比较耗时的部分&#xff0c;并且 warpPerspective 亦采用此处理。 remap 通过构建…

OpenCV 中的 remap 函数

上一篇文章中提到 warpAffine 会分块处理&#xff0c;将坐标映射和插值系数分别存储下来&#xff0c;然后借助 remap 来实现最终的映射。而 remap 会根据映射关系取源像素并加权计算出目的像素值。其最核心的计算为 RemapVec_8u。 cv::remap #mermaid-svg-uRTcIIUN4pC3vIYY .l…

ncnn 中的 warpAffine

ncnn 的仿射变换对于深度学习的预处理即小图变换进行了优化&#xff0c;速度可达到 OpenCV 的两倍。详细请参考 opencv ncnn warpaffine 性能测试。在具体实现方面&#xff0c;优点是简洁明快&#xff0c;双线性插值采用10bit 量化&#xff0c;比 OpenCV 精度高&#xff1b;缺点…

TNN MatConverter WarpAffine

TNN 的仿射变换形态介于 OpenCV 和 ncnn 之间。其处理流程与 OpenCV 较为相似并做了一些优化&#xff0c;不同的地方在于数据处理宽度为4&#xff0c;比较小。在性能表现方面中规中矩&#xff0c;小图上不及 ncnn。 MatUtils::WarpAffine #mermaid-svg-FNwIOkXOm8kxHfXI .labe…

TNN MatConverter Resize

TNN 的 resize 虽然分通道提供了多个接口&#xff0c;但底层是一起的。整个实现对于灰度图优化非常有限&#xff0c;而3通道或4通道的图像会有加速。缩放的映射关系较为简单&#xff0c;主要分为三步&#xff1a; 一维位置索引和插值系数计算&#xff1b;行内像素插值&#xf…

Hierarchical Roofline Performance Analysis for Deep Learning Applications

Roofline 模型是劳伦斯伯克利国家实验室在2008年提出的一个性能模型&#xff0c;后续很多工作亦出自该实验室。考虑到分层 Roofline 这一概念已在先前的 Hierarchical Roofline analysis for GPUs: Accelerating performance optimization for the NERSC-9 Perlmutter system 和…