前幾天被問到一個問題,相機直接採集480p的圖片和採集720p的圖片Scale爲480p的圖片誰更清晰一些。我當時的回答是
直接採集出來的480p圖片清晰一些,因爲相機採集到的yuv格式本身就是有損的(貝爾格式轉換爲YUV格式),720P轉480P也是有損的。
很明顯這並不是對方需要的答案。相機採集480P的圖片和採集720P的圖片FOV也不同,直接這樣比較應該不好比較。對方想問的問題應該是720P的相片縮放爲480P並在480P的View上顯示爲啥會比直接採集480P在480P的View上看起來更加清晰(假設採集到的廣角是一樣的)。
Why Scale
- 相機一旦開始採集就不允許動態的改變相機的採集分辨率,但是業務場景需要動態的改變分辨率,視頻會議這樣的場景用得很多。
- 用小分辨率或許本該是大分辨率纔有的廣角
- 一路採集多路使用,使用方需要的分辨率不同
Scale方案
- 相機採集到的格式可以分爲兩種,YUV和Texture。Android和iOS平臺都可以同時得到這兩種格式,Mac和Windows、Linux平臺只能得到YUV。
- YUV是每個平臺都可以拿到數據格式,可以採用第三方庫libyuv或者libswscale去做
- 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
- 可選濾波器:kFilterNone(單點採樣),kFilterLinear(單邊濾波【水平】),kFilterBilinear(雙邊濾波【水平+垂直】),kFilterBox(盒子濾波)。執行速度依次遞減,效果依次遞增
- ScaleFilterReduce函數簡單的檢查設置的濾波器是否適合
- CopyPlane當原分辨率和目標分辨率一直時直接拷貝,有彙編優化,或許比memcpy效率要高一點,沒有實際測試過
- ScalePlaneVertical當(src_w = dst_w && filtering != kFilterBox)時走這裏,僅僅採用上下濾波。
- 當目標分辨率低於原始分辨率並且滿足某種比例的時候採用執行以下函數: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 紋理過濾說明得比較清楚