![](/img/trans.png)
[英]Android Renderscript - Rotate YUV data in Renderscript
[英]Rotating YUV image data for Portrait Mode Using RenderScript
對於視頻圖像處理項目,我必須旋轉傳入的 YUV 圖像數據,以便數據不是水平顯示而是垂直顯示。 我使用了這個項目,它讓我深入了解了如何將 YUV 圖像數據轉換為 ARGB 以實時處理它們。 該項目的唯一缺點是它僅在景觀中。 沒有縱向模式選項(我不知道為什么 Google 的人提供了一個僅處理橫向的示例示例)。 我想改變這一點。
因此,我決定使用自定義的 YUV 轉 RGB 腳本來旋轉數據,使其以縱向模式顯示。 以下 GIF 演示了應用程序如何在我應用任何旋轉之前顯示數據。
你必須知道,在 Android 中,即使設備處於縱向模式,YUV 圖像數據也會以橫向呈現(我在開始這個項目之前不知道。同樣,我不明白為什么沒有可用的方法可用於通過一次調用來旋轉幀)。 這意味着即使設備處於縱向模式,起點也在左下角。 但是在縱向模式下,每一幀的起點應該在左上角。 我對字段使用矩陣表示法(例如 (0,0)、(0,1) 等)。 注意:我從這里拿了草圖:
要旋轉橫向框架,我們必須重新組織字段。 這是我對草圖(見上文)所做的映射,它顯示了橫向模式下的單幀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 = width - 1 - x
。 (我對草圖中的所有坐標進行了測試,這總是正確的)。因此,我編寫了以下渲染腳本內核函數:
#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()
。
請參閱我的YuvConverter 。 它的靈感來自android-Renderscript 將 NV12 yuv 轉換為 RGB 。
它的rs
部分非常簡單:
#pragma version(1)
#pragma rs java_package_name(whatever)
#pragma rs_fp_relaxed
rs_allocation Yplane;
uint32_t Yline;
uint32_t UVline;
rs_allocation Uplane;
rs_allocation Vplane;
rs_allocation NV21;
uint32_t Width;
uint32_t Height;
uchar4 __attribute__((kernel)) YUV420toRGB(uint32_t x, uint32_t y)
{
uchar Y = rsGetElementAt_uchar(Yplane, x + y * Yline);
uchar V = rsGetElementAt_uchar(Vplane, (x & ~1) + y/2 * UVline);
uchar U = rsGetElementAt_uchar(Uplane, (x & ~1) + y/2 * UVline);
// https://en.wikipedia.org/wiki/YCbCr#JPEG_conversion
short R = Y + (512 + 1436 * V) / 1024; // 1.402
short G = Y + (512 - 352 * U - 731 * V) / 1024; // -0.344136 -0.714136
short B = Y + (512 + 1815 * U ) / 1024; // 1.772
if (R < 0) R == 0; else if (R > 255) R == 255;
if (G < 0) G == 0; else if (G > 255) G == 255;
if (B < 0) B == 0; else if (B > 255) B == 255;
return (uchar4){R, G, B, 255};
}
uchar4 __attribute__((kernel)) YUV420toRGB_180(uint32_t x, uint32_t y)
{
return YUV420toRGB(Width - 1 - x, Height - 1 - y);
}
uchar4 __attribute__((kernel)) YUV420toRGB_90(uint32_t x, uint32_t y)
{
return YUV420toRGB(y, Width - x - 1);
}
uchar4 __attribute__((kernel)) YUV420toRGB_270(uint32_t x, uint32_t y)
{
return YUV420toRGB(Height - 1 - y, x);
}
我的 Java 包裝器在 Flutter 中使用過,但這並不重要:
public class YuvConverter implements AutoCloseable {
private RenderScript rs;
private ScriptC_yuv2rgb scriptC_yuv2rgb;
private Bitmap bmp;
YuvConverter(Context ctx, int ySize, int uvSize, int width, int height) {
rs = RenderScript.create(ctx);
scriptC_yuv2rgb = new ScriptC_yuv2rgb(rs);
init(ySize, uvSize, width, height);
}
private Allocation allocY, allocU, allocV, allocOut;
@Override
public void close() {
if (allocY != null) allocY.destroy();
if (allocU != null) allocU.destroy();
if (allocV != null) allocV.destroy();
if (allocOut != null) allocOut.destroy();
bmp = null;
allocY = null;
allocU = null;
allocV = null;
allocOut = null;
scriptC_yuv2rgb.destroy();
scriptC_yuv2rgb = null;
rs = null;
}
private void init(int ySize, int uvSize, int width, int height) {
if (bmp == null || bmp.getWidth() != width || bmp.getHeight() != height) {
bmp = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
if (allocOut != null) allocOut.destroy();
allocOut = null;
}
if (allocY == null || allocY.getBytesSize() != ySize) {
if (allocY != null) allocY.destroy();
Type.Builder yBuilder = new Type.Builder(rs, Element.U8(rs)).setX(ySize);
allocY = Allocation.createTyped(rs, yBuilder.create(), Allocation.USAGE_SCRIPT);
}
if (allocU == null || allocU.getBytesSize() != uvSize || allocV == null || allocV.getBytesSize() != uvSize ) {
if (allocU != null) allocU.destroy();
if (allocV != null) allocV.destroy();
Type.Builder uvBuilder = new Type.Builder(rs, Element.U8(rs)).setX(uvSize);
allocU = Allocation.createTyped(rs, uvBuilder.create(), Allocation.USAGE_SCRIPT);
allocV = Allocation.createTyped(rs, uvBuilder.create(), Allocation.USAGE_SCRIPT);
}
if (allocOut == null || allocOut.getBytesSize() != width*height*4) {
Type rgbType = Type.createXY(rs, Element.RGBA_8888(rs), width, height);
if (allocOut != null) allocOut.destroy();
allocOut = Allocation.createTyped(rs, rgbType, Allocation.USAGE_SCRIPT);
}
}
@Retention(RetentionPolicy.SOURCE)
// Enumerate valid values for this interface
@IntDef({Surface.ROTATION_0, Surface.ROTATION_90, Surface.ROTATION_180, Surface.ROTATION_270})
// Create an interface for validating int types
public @interface Rotation {}
/**
* Converts an YUV_420 image into Bitmap.
* @param yPlane byte[] of Y, with pixel stride 1
* @param uPlane byte[] of U, with pixel stride 2
* @param vPlane byte[] of V, with pixel stride 2
* @param yLine line stride of Y
* @param uvLine line stride of U and V
* @param width width of the output image (note that it is swapped with height for portrait rotation)
* @param height height of the output image
* @param rotation rotation to apply. ROTATION_90 is for portrait back-facing camera.
* @return RGBA_8888 Bitmap image.
*/
public Bitmap YUV420toRGB(byte[] yPlane, byte[] uPlane, byte[] vPlane,
int yLine, int uvLine, int width, int height,
@Rotation int rotation) {
init(yPlane.length, uPlane.length, width, height);
allocY.copyFrom(yPlane);
allocU.copyFrom(uPlane);
allocV.copyFrom(vPlane);
scriptC_yuv2rgb.set_Width(width);
scriptC_yuv2rgb.set_Height(height);
scriptC_yuv2rgb.set_Yline(yLine);
scriptC_yuv2rgb.set_UVline(uvLine);
scriptC_yuv2rgb.set_Yplane(allocY);
scriptC_yuv2rgb.set_Uplane(allocU);
scriptC_yuv2rgb.set_Vplane(allocV);
switch (rotation) {
case Surface.ROTATION_0:
scriptC_yuv2rgb.forEach_YUV420toRGB(allocOut);
break;
case Surface.ROTATION_90:
scriptC_yuv2rgb.forEach_YUV420toRGB_90(allocOut);
break;
case Surface.ROTATION_180:
scriptC_yuv2rgb.forEach_YUV420toRGB_180(allocOut);
break;
case Surface.ROTATION_270:
scriptC_yuv2rgb.forEach_YUV420toRGB_270(allocOut);
break;
}
allocOut.copyTo(bmp);
return bmp;
}
}
性能的關鍵是 renderscript 可以初始化一次(這就是YuvConverter.init()
是public的原因)並且以下調用非常快。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.