TNN MatConverter CvtColor NV21TOBGR

15 篇文章 3 订阅
3 篇文章 2 订阅

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_flagstd::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 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;

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(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

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

循环内每次处理两个像素。
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) =261 747474025129102520 (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;
    }

参考资料:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值