【发布时间】:2020-01-07 16:40:46
【问题描述】:
对于视频图像处理项目,我必须旋转传入的 YUV 图像数据,以便数据不是水平显示而是垂直显示。 我使用了this 项目,它让我对如何将 YUV 图像数据转换为 ARGB 以进行实时处理有了深刻的了解。 该项目的唯一缺点是它只在景观中。纵向模式没有选项(我不知道为什么 Google 的人会提供一个仅处理横向的示例示例)。我想改变它。
因此,我决定使用自定义 YUV 转 RGB 脚本来旋转数据,使其以纵向模式显示。以下 GIF 演示了应用在我应用任何旋转之前如何显示数据。
你必须知道,在Android中,即使设备处于纵向模式,YUV图像数据也是横向呈现的(我在开始这个项目之前并不知道。再次,我不明白为什么没有可用的方法,可用于一次调用旋转框架)。 这意味着即使设备处于纵向模式,起点也位于左下角。但在纵向模式下,每一帧的起点应该在左上角。我对字段使用矩阵表示法(例如(0,0)、(0,1)等)。注意:我从here 获取了草图:
要旋转横向框架,我们必须重新组织字段。这是我对草图所做的映射(见上文),它在横向模式下显示了单个帧 yuv_420。映射应将框架旋转 90 度:
first column starting from the bottom-left corner and going upwards:
(0,0) -> (0,5) // (0,0) should be at (0,5)
(0,1) -> (1,5) // (0,1) should be at (1,5)
(0,2) -> (2,5) // and so on ..
(0,3) -> (3,5)
(0,4) -> (4,5)
(0,5) -> (5,5)
2nd column starting at (1,0) and going upwards:
(1,0) -> (0,4)
(1,1) -> (1,4)
(1,2) -> (2,4)
(1,3) -> (3,4)
(1,4) -> (4,4)
(1,5) -> (5,4)
and so on...
实际上,发生的情况是第一列成为新的第一行,第二列成为新的第二行,依此类推。 从映射中可以看出,我们可以进行以下观察:
- 结果的
x坐标总是等于y坐标 从左侧。所以,我们可以说x = y。 - 我们总能观察到的是,对于结果的 y 坐标
以下等式必须成立:
y = width - 1 - x。 (我对草图中的所有坐标进行了测试,结果始终正确)。
所以,我写了下面的renderscript内核函数:
#pragma version(1)
#pragma rs java_package_name(com.jon.condino.testing.renderscript)
#pragma rs_fp_relaxed
rs_allocation gCurrentFrame;
int width;
uchar4 __attribute__((kernel)) yuv2rgbFrames(uint32_t x,uint32_t y)
{
uint32_t inX = y; // 1st observation: set x=y
uint32_t inY = width - 1 - x; // 2nd observation: the equation mentioned above
// the remaining lines are just methods to retrieve the YUV pixel elements, converting them to RGB and outputting them as result
// Read in pixel values from latest frame - YUV color space
// The functions rsGetElementAtYuv_uchar_? require API 18
uchar4 curPixel;
curPixel.r = rsGetElementAtYuv_uchar_Y(gCurrentFrame, inX, inY);
curPixel.g = rsGetElementAtYuv_uchar_U(gCurrentFrame, inX, inY);
curPixel.b = rsGetElementAtYuv_uchar_V(gCurrentFrame, inX, inY);
// uchar4 rsYuvToRGBA_uchar4(uchar y, uchar u, uchar v);
// This function uses the NTSC formulae to convert YUV to RBG
uchar4 out = rsYuvToRGBA_uchar4(curPixel.r, curPixel.g, curPixel.b);
return out;
}
该方法似乎有效,但它有一个小错误,如下图所示。如我们所见,相机预览处于纵向模式。但是我的相机预览左侧有一条非常奇怪的颜色线。为什么会这样? (请注意,我使用后置摄像头):
任何解决问题的建议都会很棒。自 2 周以来,我一直在处理这个问题(YUV 从横向到纵向的旋转),这是迄今为止我自己能得到的最好的解决方案。希望有大神帮忙改进一下代码,让左边奇怪的彩线也消失。
更新:
我在代码中所做的分配如下:
// yuvInAlloc will be the Allocation that will get the YUV image data
// from the camera
yuvInAlloc = createYuvIoInputAlloc(rs, x, y, ImageFormat.YUV_420_888);
yuvInAlloc.setOnBufferAvailableListener(this);
// here the createYuvIoInputAlloc() method
public Allocation createYuvIoInputAlloc(RenderScript rs, int x, int y, int yuvFormat) {
return Allocation.createTyped(rs, createYuvType(rs, x, y, yuvFormat),
Allocation.USAGE_IO_INPUT | Allocation.USAGE_SCRIPT);
}
// the custom script will convert the YUV to RGBA and put it to this Allocation
rgbInAlloc = RsUtil.createRgbAlloc(rs, x, y);
// here the createRgbAlloc() method
public Allocation createRgbAlloc(RenderScript rs, int x, int y) {
return Allocation.createTyped(rs, createType(rs, Element.RGBA_8888(rs), x, y));
}
// the allocation to which we put all the processed image data
rgbOutAlloc = RsUtil.createRgbIoOutputAlloc(rs, x, y);
// here the createRgbIoOutputAlloc() method
public Allocation createRgbIoOutputAlloc(RenderScript rs, int x, int y) {
return Allocation.createTyped(rs, createType(rs, Element.RGBA_8888(rs), x, y),
Allocation.USAGE_IO_OUTPUT | Allocation.USAGE_SCRIPT);
}
其他一些辅助函数:
public Type createType(RenderScript rs, Element e, int x, int y) {
if (Build.VERSION.SDK_INT >= 21) {
return Type.createXY(rs, e, x, y);
} else {
return new Type.Builder(rs, e).setX(x).setY(y).create();
}
}
@RequiresApi(18)
public Type createYuvType(RenderScript rs, int x, int y, int yuvFormat) {
boolean supported = yuvFormat == ImageFormat.NV21 || yuvFormat == ImageFormat.YV12;
if (Build.VERSION.SDK_INT >= 19) {
supported |= yuvFormat == ImageFormat.YUV_420_888;
}
if (!supported) {
throw new IllegalArgumentException("invalid yuv format: " + yuvFormat);
}
return new Type.Builder(rs, createYuvElement(rs)).setX(x).setY(y).setYuvFormat(yuvFormat)
.create();
}
public Element createYuvElement(RenderScript rs) {
if (Build.VERSION.SDK_INT >= 19) {
return Element.YUV(rs);
} else {
return Element.createPixel(rs, Element.DataType.UNSIGNED_8, Element.DataKind.PIXEL_YUV);
}
}
调用自定义渲染脚本和分配:
// see below how the input size is determined
customYUVToRGBAConverter.invoke_setInputImageSize(x, y);
customYUVToRGBAConverter.set_inputAllocation(yuvInAlloc);
// receive some frames
yuvInAlloc.ioReceive();
// performs the conversion from the YUV to RGB
customYUVToRGBAConverter.forEach_convert(rgbInAlloc);
// this just do the frame manipulation , e.g. applying a particular filter
renderer.renderFrame(mRs, rgbInAlloc, rgbOutAlloc);
// send manipulated data to output stream
rgbOutAlloc.ioSend();
最后但最不重要的是输入图像的大小。您在上面看到的方法的 x 和 y 坐标基于此处表示为 mPreviewSize 的预览大小:
int deviceOrientation = getWindowManager().getDefaultDisplay().getRotation();
int totalRotation = sensorToDeviceRotation(cameraCharacteristics, deviceOrientation);
// determine if we are in portrait mode
boolean swapRotation = totalRotation == 90 || totalRotation == 270;
int rotatedWidth = width;
int rotatedHeigth = height;
// are we in portrait mode? If yes, then swap the values
if(swapRotation){
rotatedWidth = height;
rotatedHeigth = width;
}
// determine the preview size
mPreviewSize = chooseOptimalSize(
map.getOutputSizes(SurfaceTexture.class),
rotatedWidth,
rotatedHeigth);
因此,在我的情况下,x 将是 mPreviewSize.getWidth() 和 y 将是 mPreviewSize.getHeight()。
【问题讨论】:
-
请显示您对此渲染脚本的分配。您是否在旋转图像时交换了输出分配的宽度和高度?
-
@AlexanderUshakov:我在上面创建了一个更新部分,其中包含所有分配的内容。如果有什么遗漏,请现在告诉我。简短地回答您的问题:是的,我检查设备是否旋转并交换值。但这也在上面的更新部分。
-
请注意,对于相机图像,NTSC (BT.601) 转换公式不正确。你需要JPEG conversion。
标签: android android-camera2 renderscript yuv landscape-portrait