[BUG]: ColorConvert YUV -> YUV bit-depth conversion to 16u puts neutral chroma at 32800 instead of 32768
Nessuno ha ancora preso questa issue.
Valutazione
- Difficoltà
- 2/5
- Tempo stimato
- 1-3 ore
- Idoneità per principianti
- 78/100
- Tipo di issue
- Bug
- Chiarezza
- Specificata chiaramente
- Stato di attività
- Attiva
- Stack tecnologico
- cpp
- Ambito
- backend, computer-vision
Direzione di ricerca
Leggi color_conversion_operations.hpp intorno alle righe 186–189 e confronta la conversione di profondità nello stesso spazio colore con la gestione del punto medio alle righe 117 e 142. Riproduci la conversione YUV neutra dell’issue, poi esamina 01_color_conversion/image_conversion_example.cu per individuare il controllo di regressione proposto. Il lavoro è completo quando U e V neutri vengono convertiti in 32768 per input 8u e 10u e il test ha esito positivo.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
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.
- Lingua principale
- C++
- Stelle
- 18
- Fork
- 6
- Metriche di merge delle PR
- Nessuna PR unita negli ultimi 30g
Preparare l'ambiente
- Nessun Dockerfile né file Docker Compose
- Nessun modello di pull request
- Leggi la guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Issue simili
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 62/100
bazel-contrib/rules_go#4784 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
documentation kind: bug
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 68/100
I maintainer di solito rispondono entro 1 giorno
-
TUIs: Fix TUI CMakesApertabug
Difficoltà 2/5 1-3 ore Idoneità per principianti 62/100
RMIT-Rover-Team/equinox-2#145 ·
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 62/100
eunomia-bpf/bpftime#779 ·
I maintainer di solito rispondono entro 1 giorno