You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

YCbCr转RGB图像损坏排查及解决:算术溢出/下溢修复

YCbCr转RGB图像损坏问题:解决记录与原问题

更新:问题已解决。我的YCbCr转RGB函数未先检查算术溢出与下溢问题。感谢所有回复者!我将保留问题代码并附上解决方案,以便其他开发者参考。

int willOverflow(int16_t result)
{
  return result > 255;
}
int willUnderflow(int16_t result)
{
  return result < 0;
}

void yCbCrToRgb(uint8_t* yCbCr, uint8_t* targetRgb)
{
    uint8_t luma = yCbCr[0];
    uint8_t Cb = yCbCr[1];
    uint8_t Cr = yCbCr[2];

    if (!willOverflow(luma + (1.402 * (Cr - 128))) && !willUnderflow(luma + (1.402 * (Cr - 128)))) targetRgb[0] = luma + (1.402 * (Cr - 128));
    else targetRgb[0] = willUnderflow(luma + (1.402 * (Cr - 128))) ? 0 : 255;

    if (!willOverflow(luma + (-0.344136 * (Cb - 128)) + (-0.714136 * (Cr - 128))) && !willUnderflow(luma + (-0.344136 * (Cb - 128)) + (-0.714136 * (Cr - 128)))) targetRgb[1] = luma + (-0.344136 * (Cb - 128)) + (-0.714136 * (Cr - 128));
    else targetRgb[1] = willUnderflow(luma + (-0.344136 * (Cb - 128)) + (-0.714136 * (Cr - 128))) ? 0 : 255;

    if (!willOverflow(luma + (1.772 * (Cb - 128))) && !willUnderflow(luma + (1.772 * (Cb - 128)))) targetRgb[2] = luma + (1.772 * (Cb - 128));
    else targetRgb[2] = willUnderflow(luma + (1.772 * (Cb - 128))) ? 0 : 255;
}

原问题描述

我是一名C语言新手开发者,首次在Stack Overflow发帖,正在编写JPEG编码器。目前已实现将ppm文件中的原始RGB像素数据转换为YCbCr色彩空间,并按照4:2:0子采样规则对蓝色(Cb)和红色(Cr)色度通道进行子采样。处理后亮度(Y)通道的采样数与图像像素数一致,两个色度通道的采样数仅为像素数的1/4。非子采样的YCbCr转RGB过程正常,但将这三个通道重组后转RGB并写入输出文件时,出现明显的图像损坏:部分图像仅在亮区与暗区交界处存在异常颜色像素,其余区域正常;部分图像则同时存在正常区域与大面积损坏区域。在进行值中心化及离散余弦变换前,我希望确保子采样与转RGB的流程正常。

图像相关结构体定义

// 以下是我用于表示图像的结构体
typedef struct ppmHeader
{
  uint32_t numPixels;
  uint16_t pixelWidth, pixelHeight;
  uint8_t maxColorVal;
  size_t headerBytes, pixelDataSize, bytesPerPixel, rowSize;
  uint8_t* headerStr;
} ppmHeader;

typedef struct YCBCR_Pixel_24
{
  uint8_t yCbCr[YCBCR_BYTES]; // 3字节
} YCBCR_Pixel_24;

typedef struct intermediateImg
{
  YCBCR_Pixel_24* pixelData; 
  int16_t* lumaSample;
  int16_t* CbSample;
  int16_t* CrSample;
} intermediateImg;

RGB转YCbCr实现

// 将RGB转换为YCbCr,转换及逆转换公式均来自MATLAB
void setYCbCr(YCBCR_Pixel_24* pixel, uint8_t* rgbIn)
{
  uint8_t luminance, Cb, Cr;
  uint8_t r, g, b;

  r = rgbIn[0];
  g = rgbIn[1];
  b = rgbIn[2];

  luminance = 0.257 * r + 0.504 * g + 0.098 * b + 16;
  Cb = -0.148 * r - 0.291 * g + 0.439 * b + 128;
  Cr = 0.439 * r - 0.368 * g - 0.071 * b + 128;

  pixel->yCbCr[0] = luminance;
  pixel->yCbCr[1] = Cb;
  pixel->yCbCr[2] = Cr;
}

核心实现代码

/* 4:2:0子采样:每2x2像素块中取1个Cb和Cr色度样本 */
void subsampleChrominance(uint16_t imgWidth, uint32_t subSampledSize, intermediateImg* img)
{
  uint32_t ssIndex = 0; //子采样索引
  uint32_t pdIndex = 0; //像素数据索引
  
  while (ssIndex < subSampledSize)
  {
    img->CbSample[ssIndex] = img->pixelData[pdIndex].yCbCr[1];
    img->CrSample[ssIndex] = img->pixelData[pdIndex].yCbCr[2];

    if ((pdIndex + 2) % imgWidth == 0)
    {
      pdIndex = (pdIndex + 2) + imgWidth;
    } 
    else pdIndex += 2;

    ssIndex++;
  }
}


/* 模拟从4:2:0子采样的色度信号重建完整YCbCr图像 */
void writeFromSignals(intermediateImg* img, ppmHeader* imgSpecs, FILE* target)
{
  uint32_t pdIndex, ssIndex, rowCount;
  pdIndex = ssIndex = rowCount = 0;
  uint32_t samplesPerRow = imgSpecs->pixelWidth / 2;

  while (pdIndex < imgSpecs->numPixels)
  {
    img->pixelData[pdIndex].yCbCr[0] = img->lumaSample[pdIndex];
    img->pixelData[pdIndex].yCbCr[1] = img->CbSample[ssIndex];
    img->pixelData[pdIndex].yCbCr[2] = img->CrSample[ssIndex];
    
    if ((pdIndex+1) % imgSpecs->pixelWidth == 0) 
    {
      rowCount++;
      if (rowCount % 2 != 0) ssIndex -= (samplesPerRow-1);
      else ssIndex++;
      pdIndex++;
      continue;
    }

    pdIndex++;
    if (pdIndex % 2 == 0) ssIndex++;
  }
  writeIntermediate_rgb(img, imgSpecs, target);
}

/* 将YCbCr转换为RGB并写入文件 */
void writeIntermediate_rgb(intermediateImg* img, ppmHeader* specs, FILE* target)
{
  uint32_t i = 0;
  uint32_t sI = 0;
  uint8_t rgb[BYTES_PER_PIXEL_24]; // 3字节
  
  fwrite(specs->headerStr, 1, specs->headerBytes, target);
  while (i < specs->numPixels)
  {
    yCbCrToRgb(&img->pixelData[i].yCbCr[0], &rgb[0]);
    fwrite(&rgb[0], 1, BYTES_PER_PIXEL_24, target);
    i++;
    if (i % 2 == 0) sI++;
  }
}

/* 未处理溢出的YCbCr转RGB函数 */
void yCbCrToRgb(uint8_t* yCbCr, uint8_t* targetRgb)
{
  uint8_t luma = yCbCr[0];
  uint8_t Cb, Cr;
  Cb = yCbCr[1];
  Cr = yCbCr[2];
  
  targetRgb[0] = 1.164 * (luma - 16) + 1.596 * (Cr - 128);
  targetRgb[1] = 1.164 * (luma - 16) - 0.813 * (Cr - 128) - 0.392 * (Cb - 128);
  targetRgb[2] = 1.164 * (luma - 16) + 2.017 * (Cb - 128);
}

程序执行流程

  • 程序读取图像到内存,将每个3字节RGB像素转换为3字节YCbCr像素,存储在intermediateImg的pixelData字段,Y分量存入lumaSample字段。
  • 调用subsampleChrominance填充CbSample和CrSample字段。
  • 调用writeFromSignals从子采样信号重建完整图像。

问题排查情况

我已有从非子采样YCbCr重建正常图像的函数,理解子采样是有损的,但同类示例中转RGB后图像与原图几乎一致。目前已验证色度样本分配逻辑正确,YCbCr转RGB公式无误,尝试将采样数据类型从int16_t改为uint8_t无改善,怀疑writeFromSignals的指针算术存在问题,但小图测试未发现异常。

损坏图像现象

  • 图像亮区出现异常青色像素,损坏程度较轻
  • 图像暗区存在大量噪点,损坏程度较重

我可能忽略了某些明显问题,也不熟悉此类流程的调试方法,可提供更多代码或信息,感谢各位开发者的帮助。


内容的提问来源于stack exchange,提问作者tednickels

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 05:27:02