图片Scale(YUV/RGB)


前几天被问到一个问题,相机直接采集480p的图片和采集720p的图片Scale为480p的图片谁更清晰一些。我当时的回答是 直接采集出来的480p图片清晰一些,因为相机采集到的yuv格式本身就是有损的(贝尔格式转换为YUV格式),720P转480P也是有损的。很明显这并不是对方需要的答案。相机采集480P的图片和采集720P的图片 FOV也不同,直接这样比较应该不好比较。对方想问的问题应该是720P的相片缩放为480P并在480P的View上显示为啥会比直接采集480P在480P的View上看起来更加清晰(假设采集到的广角是一样的)。

Why Scale

  1. 相机一旦开始采集就不允许动态的改变相机的采集分辨率,但是业务场景需要动态的改变分辨率,视频会议这样的场景用得很多。
  2. 用小分辨率或许本该是大分辨率才有的广角
  3. 一路采集多路使用,使用方需要的分辨率不同

Scale方案

  1. 相机采集到的格式可以分为两种,YUV和Texture。Android和iOS平台都可以同时得到这两种格式,Mac和Windows、Linux平台只能得到YUV。
  2. YUV是每个平台都可以拿到数据格式,可以采用第三方库libyuv或者libswscale去做
  3. Texture缩放就是OpenGL的方案,重新画到另外一个Texture上就好了,Android平台拿到Texture是oes类型的,需要在shader中区分一下

libyuv的实现

以I420缩放为例,实现代码就是这个函数I420Scale,能看懂代码,但是我并不知道为啥要这样做。

  • I420Scale
    YUV是分层结构,I420就是三层,所以会分别去缩放每一层(调用ScalePlane去缩放)
LIBYUV_API
int I420Scale(const uint8* src_y, int src_stride_y,  const uint8* src_u,  int src_stride_u, const uint8* src_v,  int src_stride_v, int src_width,  int src_height,  uint8* dst_y,  int dst_stride_y,  uint8* dst_u, int dst_stride_u, uint8* dst_v,  int dst_stride_v, int dst_width, int dst_height, enum FilterMode filtering) {
  int src_halfwidth = SUBSAMPLE(src_width, 1, 1);
  int src_halfheight = SUBSAMPLE(src_height, 1, 1);
  int dst_halfwidth = SUBSAMPLE(dst_width, 1, 1);
  int dst_halfheight = SUBSAMPLE(dst_height, 1, 1);
  if (!src_y || !src_u || !src_v || src_width == 0 || src_height == 0 ||
      src_width > 32768 || src_height > 32768 || !dst_y || !dst_u || !dst_v ||
      dst_width <= 0 || dst_height <= 0) {
    return -1;
  }

  ScalePlane(src_y, src_stride_y, src_width, src_height, dst_y, dst_stride_y, dst_width, dst_height, filtering);
  ScalePlane(src_u, src_stride_u, src_halfwidth, src_halfheight, dst_u, dst_stride_u, dst_halfwidth, dst_halfheight, filtering);
  ScalePlane(src_v, src_stride_v, src_halfwidth, src_halfheight, dst_v, dst_stride_v, dst_halfwidth, dst_halfheight, filtering);
  return 0;
}
  • ScalePlane
  1. 可选滤波器:kFilterNone(单点采样),kFilterLinear(单边滤波【水平】),kFilterBilinear(双边滤波【水平+垂直】),kFilterBox(盒子滤波)。执行速度依次递减,效果依次递增
  2. ScaleFilterReduce函数简单的检查设置的滤波器是否适合
  3. CopyPlane当原分辨率和目标分辨率一直时直接拷贝,有汇编优化,或许比memcpy效率要高一点,没有实际测试过
  4. ScalePlaneVertical当(src_w = dst_w && filtering != kFilterBox)时走这里,仅仅采用上下滤波。
  5. 当目标分辨率低于原始分辨率并且满足某种比例的时候采用执行以下函数:ScalePlaneDown34(3/4),ScalePlaneDown2(1/2),ScalePlaneDown38(3/8),ScalePlaneDown4(1/4)
LIBYUV_API
void ScalePlane(const uint8* src,
                int src_stride,
                int src_width,
                int src_height,
                uint8* dst,
                int dst_stride,
                int dst_width,
                int dst_height,
                enum FilterMode filtering) {
  // Simplify filtering when possible.
  filtering = ScaleFilterReduce(src_width, src_height, dst_width, dst_height,
                                filtering);

  // Negative height means invert the image.
  if (src_height < 0) {
    src_height = -src_height;
    src = src + (src_height - 1) * src_stride;
    src_stride = -src_stride;
  }

  // Use specialized scales to improve performance for common resolutions.
  // For example, all the 1/2 scalings will use ScalePlaneDown2()
  if (dst_width == src_width && dst_height == src_height) {
    // Straight copy.
    CopyPlane(src, src_stride, dst, dst_stride, dst_width, dst_height);
    return;
  }
  if (dst_width == src_width && filtering != kFilterBox) {
    int dy = FixedDiv(src_height, dst_height);
    // Arbitrary scale vertically, but unscaled horizontally.
    ScalePlaneVertical(src_height, dst_width, dst_height, src_stride,
                       dst_stride, src, dst, 0, 0, dy, 1, filtering);
    return;
  }
  if (dst_width <= Abs(src_width) && dst_height <= src_height) {
    // Scale down.
    if (4 * dst_width == 3 * src_width && 4 * dst_height == 3 * src_height) {
      // optimized, 3/4
      ScalePlaneDown34(src_width, src_height, dst_width, dst_height, src_stride,
                       dst_stride, src, dst, filtering);
      return;
    }
    if (2 * dst_width == src_width && 2 * dst_height == src_height) {
      // optimized, 1/2
      ScalePlaneDown2(src_width, src_height, dst_width, dst_height, src_stride,
                      dst_stride, src, dst, filtering);
      return;
    }
    // 3/8 rounded up for odd sized chroma height.
    if (8 * dst_width == 3 * src_width && 8 * dst_height == 3 * src_height) {
      // optimized, 3/8
      ScalePlaneDown38(src_width, src_height, dst_width, dst_height, src_stride,
                       dst_stride, src, dst, filtering);
      return;
    }
    if (4 * dst_width == src_width && 4 * dst_height == src_height &&
        (filtering == kFilterBox || filtering == kFilterNone)) {
      // optimized, 1/4
      ScalePlaneDown4(src_width, src_height, dst_width, dst_height, src_stride,
                      dst_stride, src, dst, filtering);
      return;
    }
  }
  if (filtering == kFilterBox && dst_height * 2 < src_height) {
    ScalePlaneBox(src_width, src_height, dst_width, dst_height, src_stride,
                  dst_stride, src, dst);
    return;
  }
  if (filtering && dst_height > src_height) {
    ScalePlaneBilinearUp(src_width, src_height, dst_width, dst_height,
                         src_stride, dst_stride, src, dst, filtering);
    return;
  }
  if (filtering) {
    ScalePlaneBilinearDown(src_width, src_height, dst_width, dst_height,
                           src_stride, dst_stride, src, dst, filtering);
    return;
  }
  ScalePlaneSimple(src_width, src_height, dst_width, dst_height, src_stride,
                   dst_stride, src, dst);
}
  • 单边滤波(水平)
    取相邻点进行混合
// (1-f)a + fb can be replaced with a + f(b-a)
#if defined(__arm__) || defined(__aarch64__)
#define BLENDER(a, b, f) \
  (uint8)((int)(a) + ((((int)((f)) * ((int)(b) - (int)(a))) + 0x8000) >> 16))
#else
// Intel uses 7 bit math with rounding.
#define BLENDER(a, b, f) \
  (uint8)((int)(a) + (((int)((f) >> 9) * ((int)(b) - (int)(a)) + 0x40) >> 7))
#endif

void ScaleFilterCols_C(uint8* dst_ptr,
                       const uint8* src_ptr,
                       int dst_width,
                       int x,
                       int dx) {
  int j;
  for (j = 0; j < dst_width - 1; j += 2) {
    int xi = x >> 16;
    int a = src_ptr[xi];
    int b = src_ptr[xi + 1];
    dst_ptr[0] = BLENDER(a, b, x & 0xffff);
    x += dx;
    xi = x >> 16;
    a = src_ptr[xi];
    b = src_ptr[xi + 1];
    dst_ptr[1] = BLENDER(a, b, x & 0xffff);
    x += dx;
    dst_ptr += 2;
  }
  if (dst_width & 1) {
    int xi = x >> 16;
    int a = src_ptr[xi];
    int b = src_ptr[xi + 1];
    dst_ptr[0] = BLENDER(a, b, x & 0xffff);
  }
}
  • 单边滤波(垂直)
    取上下亮点进行混合,上下两点的比例每行都不一样,具体看代码
void InterpolateRow_C(uint8* dst_ptr,
                      const uint8* src_ptr,
                      ptrdiff_t src_stride,
                      int width,
                      int source_y_fraction) {
  int y1_fraction = source_y_fraction;
  int y0_fraction = 256 - y1_fraction;
  const uint8* src_ptr1 = src_ptr + src_stride;
  int x;
  if (y1_fraction == 0) {
    memcpy(dst_ptr, src_ptr, width);
    return;
  }
  if (y1_fraction == 128) {
    HalfRow_C(src_ptr, src_stride, dst_ptr, width);
    return;
  }
  for (x = 0; x < width - 1; x += 2) {
    dst_ptr[0] =
        (src_ptr[0] * y0_fraction + src_ptr1[0] * y1_fraction + 128) >> 8;
    dst_ptr[1] =
        (src_ptr[1] * y0_fraction + src_ptr1[1] * y1_fraction + 128) >> 8;
    src_ptr += 2;
    src_ptr1 += 2;
    dst_ptr += 2;
  }
  if (width & 1) {
    dst_ptr[0] =
        (src_ptr[0] * y0_fraction + src_ptr1[0] * y1_fraction + 128) >> 8;
  }
}
  • box滤波
    就是根据上下多个点(3个及以上,根据分辨率而定)混合得到一个点
要将YUV422格式转换为RGB格式,您可以使用FFmpeg库中的函数进行转换。以下是一个使用FFmpeg进行YUV422到RGB转换的示例代码: ```c #include <stdio.h> #include <libavutil/imgutils.h> #include <libswscale/swscale.h> int main() { int width = 640; // 输入图像的宽度 int height = 480; // 输入图像的高度 // 分配输入图像的内存 uint8_t* yuv422Buffer = (uint8_t*)malloc(width * height * 2); // 在这里填充YUV422数据到yuv422Buffer // 分配输出图像的内存 uint8_t* rgbBuffer = (uint8_t*)malloc(width * height * 3); // 创建输入图像的AVFrame AVFrame* yuv422Frame = av_frame_alloc(); yuv422Frame->format = AV_PIX_FMT_YUV422P; yuv422Frame->width = width; yuv422Frame->height = height; av_image_fill_arrays(yuv422Frame->data, yuv422Frame->linesize, yuv422Buffer, AV_PIX_FMT_YUV422P, width, height, 1); // 创建输出图像的AVFrame AVFrame* rgbFrame = av_frame_alloc(); rgbFrame->format = AV_PIX_FMT_RGB24; rgbFrame->width = width; rgbFrame->height = height; av_image_fill_arrays(rgbFrame->data, rgbFrame->linesize, rgbBuffer, AV_PIX_FMT_RGB24, width, height, 1); // 创建转换上下文 struct SwsContext* swsContext = sws_getContext(width, height, AV_PIX_FMT_YUV422P, width, height, AV_PIX_FMT_RGB24, 0, NULL, NULL, NULL); // 进行转换 sws_scale(swsContext, yuv422Frame->data, yuv422Frame->linesize, 0, height, rgbFrame->data, rgbFrame->linesize); // 在这里可以使用rgbBuffer中的RGB数据 // 释放资源 free(yuv422Buffer); free(rgbBuffer); av_frame_free(&yuv422Frame); av_frame_free(&rgbFrame); sws_freeContext(swsContext); return 0; } ``` 请注意,此示例假设您已经填充了正确的YUV422数据到yuv422Buffer。您需要根据实际情况修改输入图像的宽度和高度,并在转换后使用rgbBuffer中的RGB数据进行进一步处理。还要确保您已经链接了FFmpeg库并包含所需的头文件。 请参考FFmpeg文档以获得更多关于使用FFmpeg进行图像格式转换的详细信息和选项。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值