图片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个及以上,根据分辨率而定)混合得到一个点

GL实现方案

设置Texture的属性GL_TEXTURE_MIN_FILTER和GL_TEXTURE_MAG_FILTER。可选的属性有GL_LINEAR(类似box),GL_LINEAR_MIPMAP_NEAREST(类似水平),GL_LINEAR_MIPMAP_LINEAR(类似上下),learnopengl 纹理过滤说明得比较清楚

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章