[BUG]: ColorConvert YUV -> YUV bit-depth conversion to 16u puts neutral chroma at 32800 instead of 32768

Đang mở Phù hợp với người mới
#4 0 bình luận 0 reaction 0 người được giao Xem trên GitHub

Chưa có ai nhận issue này.

Đánh giá

Độ khó
2/5
Thời gian dự kiến
1-3 giờ
Mức phù hợp với người mới
78/100
Loại issue
Lỗi
Độ rõ ràng
Đặc tả rõ ràng
Mức độ hoạt động
Sôi nổi
Công nghệ
cpp
Lĩnh vực
backend, computer-vision

Hướng nghiên cứu

Đọc color_conversion_operations.hpp quanh các dòng 186–189 và so sánh chuyển đổi độ sâu trong cùng không gian màu với cách xử lý điểm giữa ở các dòng 117 và 142. Tái tạo chuyển đổi YUV trung tính trong issue, sau đó kiểm tra 01_color_conversion/image_conversion_example.cu để tìm phần kiểm tra hồi quy được đề xuất. Công việc hoàn tất khi U và V trung tính được chuyển thành 32768 với đầu vào 8u và 10u, đồng thời bài kiểm tra chạy thành công.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Mô tả

Describe the bug.

ColorConvert<CS, CS, InDepth, OutDepth> with a YUV color space and only a bit-depth change scales U and V by the depth factor about zero, the same way it scales Y. Chroma is centered on the midpoint, so for 8u -> 16u (factor 256.246) and 10u -> 16u (factor 64.06) neutral chroma 128 / 512 becomes 32800. The 16-bit neutral value is 32768. That is the value used by midpoint_gray<bpp_16u> and by range_values<bit_depth::bpp_16u>.chroma_mid(). It is also what the YUV -> RGB path subtracts.

As a result, gray stops being gray when it goes through 16-bit YUV:

  • 8u YUV -> 16u RGB in one step: R = G = B = 32800
  • 8u YUV -> 16u YUV -> 16u RGB: R=32844 G=32766 B=32855

I expected U = V = 32768 in the 16-bit YUV output, and the same RGB from both paths.

The YUV <-> RGB paths already handle the midpoint (color_conversion_operations.hpp:117 subtracts midpoint_gray<InD>, :142 adds midpoint_gray<OutD>). Only the same-color-space branch (:186-189, b *= depth_scale; c *= depth_scale;) does not. 8u <-> 10u is unaffected because ×4 maps 128 to 512 exactly.

Minimum reproducible example
// nvcc -std=c++17 -arch=sm_103a --expt-relaxed-constexpr -DARCH=1030 -I<NPPDx>/include repro.cu
#include <nppdx.hpp>
#include <cstdio>
#include <cstring>
#include <vector>
using namespace nppdx;
constexpr int Arch = ARCH, W = 16, H = 16;

template<typename In, typename Op1, typename Op2, typename Out>
__global__ void kernel(const uint8_t* in, uint8_t* out, int w, int h) {
    float regs[In::elements_per_thread];
    In().execute(in, regs, w, h);
    Op1().execute(regs, w, h);
    Op2().execute(regs, w, h);
    Out().execute(regs, out, w, h);
}
template<packing_format F>
using Ingest = decltype(InputOutput<input_output_direction::ingest>() + InputFormat<F>() + TileSize<16, 16>() + SM<Arch>() + Block());
template<packing_format F>
using Exgest = decltype(InputOutput<input_output_direction::exgest>() + OutputFormat<F>() + TileSize<16, 16>() + SM<Arch>() + Block());
template<color_space I, color_space O, bit_depth DI, bit_depth DO>
using CC = decltype(Function<function::color_convert>() + ColorConvert<I, O, DI, DO>() + TileSize<16, 16>() + SM<Arch>() + Block());
using CS = color_space; using BD = bit_depth;

template<typename In, typename A, typename B, typename Out>
std::vector<uint8_t> run(const std::vector<uint8_t>& h_in, size_t out_bytes) {
    uint8_t *d_in, *d_out;
    cudaMalloc(&d_in, h_in.size()); cudaMalloc(&d_out, out_bytes);
    cudaMemcpy(d_in, h_in.data(), h_in.size(), cudaMemcpyHostToDevice);
    kernel<In, A, B, Out><<<In::calculate_grid_dim(W, H), In::block_dim>>>(d_in, d_out, W, H);
    std::vector<uint8_t> h_out(out_bytes);
    cudaMemcpy(h_out.data(), d_out, out_bytes, cudaMemcpyDeviceToHost);
    cudaFree(d_in); cudaFree(d_out);
    return h_out;
}
uint16_t u16(const std::vector<uint8_t>& b, size_t i) { uint16_t v; std::memcpy(&v, b.data() + 2 * i, 2); return v; }

int main() {
    std::vector<uint8_t> gray(3 * W * H, 128); // YUV444P 8-bit, Y = U = V = 128
    using Depth = CC<CS::yuv_bt601, CS::yuv_bt601, BD::bpp_8u, BD::bpp_16u>;
    using Nop16 = CC<CS::yuv_bt601, CS::yuv_bt601, BD::bpp_16u, BD::bpp_16u>;
    using Nop8  = CC<CS::yuv_bt601, CS::yuv_bt601, BD::bpp_8u, BD::bpp_8u>;
    auto p216 = run<Ingest<packing_format::yuv444p>, Depth, Nop16, Exgest<packing_format::p216>>(gray, W * H * 4);
    printf("YUV 8u -> YUV 16u (P216): Y=%u U=%u V=%u\n", u16(p216, 0), u16(p216, W * H), u16(p216, W * H + 1));
    auto one = run<Ingest<packing_format::yuv444p>, CC<CS::yuv_bt601, CS::rgb, BD::bpp_8u, BD::bpp_16u>, Nop8,
                   Exgest<packing_format::rgb16>>(gray, W * H * 6);
    auto two = run<Ingest<packing_format::yuv444p>, Depth, CC<CS::yuv_bt601, CS::rgb, BD::bpp_16u, BD::bpp_16u>,
                   Exgest<packing_format::rgb16>>(gray, W * H * 6);
    printf("YUV 8u -> RGB 16u           : R=%u G=%u B=%u\n", u16(one, 0), u16(one, 1), u16(one, 2));
    printf("YUV 8u -> YUV 16u -> RGB 16u: R=%u G=%u B=%u\n", u16(two, 0), u16(two, 1), u16(two, 2));
}
Environment details
NPPDx main @ e7efdc1 (v0.1.2)
nvcc 13.3 (V13.3.73), gcc 13.3, -arch=sm_103a, x86_64 Linux: the repro compiles.
The output below comes from running the same device code under a CPU emulation of the kernel
(no GPU was free when this was checked), not from a GPU run.
Relevant log output
YUV 8u -> YUV 16u (P216): Y=32800 U=32800 V=32800
YUV 8u -> RGB 16u           : R=32800 G=32800 B=32800
YUV 8u -> YUV 16u -> RGB 16u: R=32844 G=32766 B=32855
Other/Misc.

The 10u -> 16u case behaves the same way (512 -> 32800).

A possible fix: in the same-color-space branch of convert<>, rescale U and V about the midpoint for YUV, and leave Y and RGB as they are.

b = (b - midpoint_gray<InputBitDepth>::value) * depth_scale + midpoint_gray<OutputBitDepth>::value;

With that change the outputs are U = V = 32768 and R = G = B = 32800 for both paths. RGB depth conversion is unchanged.

The change keeps the existing depth factor (256.246 / 64.06) and only re-centres chroma on the midpoint, so legal 8-bit chroma 16 / 240 maps to about 4068 / 61468 (today about 4100 / 61499). That matches what the 8u YUV -> 16u RGB path does today. The regression check only looks at the midpoint, so it would also pass if you prefer a ×2^(n-8) depth mapping for YUV instead.

A fix with a regression test is ready. The test is a check in 01_color_conversion/image_conversion_example.cu: it converts neutral YUV444P/YUV444P10 to P216 and expects 32768. I can open a pull request if that is welcome.

Check for duplicates
  • I have searched the open bugs/issues and have found no duplicates for this bug report

This report was drafted with Claude Code (claude-opus-5-5); the output above is from the CPU-emulation run described.

Ngôn ngữ chính
C++
Star
18
Fork
6
Chỉ số merge pull request
Không có pull request nào được merge trong 30 ngày

Chuẩn bị môi trường

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Issue tương tự

Thêm issue về C++

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.