考虑到以下逻辑(据我理解),我编写了从YUV_420_888到Bitmap的转换:

概括地说,核坐标x和y与Y平面的非填充部分(2d-分配)的x和y以及输出位图的x和y是一致的。然而,U-和V-平面的结构与Y平面不同,因为它们使用1字节来覆盖4个像素,此外,可能有一个超过一个的PixelStride,此外,它们还可能有一个与Y平面不同的填充。因此,为了通过内核有效地访问U和V,我将它们放在一维分配中,并创建了一个索引“uvIndex”,它给出了相应的U-和V在该一维分配中的位置,即给定的(x,y)坐标在(非填充的)Y平面(以及,因此,输出位图)。
为了保持rs-内核的精瘦,我通过通过yPlane限制x-范围(这反映了y-平面的RowStride,因此可以在内核中忽略)而排除了内核中的填充区域。因此,我们只需要考虑uvPixelStride和uvRowStride在uvIndex中的作用,即用来访问u和v值的索引。
这是我的密码:
Renderscript内核,名为yuv420888.rs
#pragma version(1)
#pragma rs java_package_name(com.xxxyyy.testcamera2);
#pragma rs_fp_relaxed
int32_t width;
int32_t height;
uint picWidth, uvPixelStride, uvRowStride ;
rs_allocation ypsIn,uIn,vIn;
// The LaunchOptions ensure that the Kernel does not enter the padding zone of Y, so yRowStride can be ignored WITHIN the Kernel.
uchar4 __attribute__((kernel)) doConvert(uint32_t x, uint32_t y) {
// index for accessing the uIn's and vIn's
uint uvIndex= uvPixelStride * (x/2) + uvRowStride*(y/2);
// get the y,u,v values
uchar yps= rsGetElementAt_uchar(ypsIn, x, y);
uchar u= rsGetElementAt_uchar(uIn, uvIndex);
uchar v= rsGetElementAt_uchar(vIn, uvIndex);
// calc argb
int4 argb;
argb.r = yps + v * 1436 / 1024 - 179;
argb.g = yps -u * 46549 / 131072 + 44 -v * 93604 / 131072 + 91;
argb.b = yps +u * 1814 / 1024 - 227;
argb.a = 255;
uchar4 out = convert_uchar4(clamp(argb, 0, 255));
return out;
}Java侧:
private Bitmap YUV_420_888_toRGB(Image image, int width, int height){
// Get the three image planes
Image.Plane[] planes = image.getPlanes();
ByteBuffer buffer = planes[0].getBuffer();
byte[] y = new byte[buffer.remaining()];
buffer.get(y);
buffer = planes[1].getBuffer();
byte[] u = new byte[buffer.remaining()];
buffer.get(u);
buffer = planes[2].getBuffer();
byte[] v = new byte[buffer.remaining()];
buffer.get(v);
// get the relevant RowStrides and PixelStrides
// (we know from documentation that PixelStride is 1 for y)
int yRowStride= planes[0].getRowStride();
int uvRowStride= planes[1].getRowStride(); // we know from documentation that RowStride is the same for u and v.
int uvPixelStride= planes[1].getPixelStride(); // we know from documentation that PixelStride is the same for u and v.
// rs creation just for demo. Create rs just once in onCreate and use it again.
RenderScript rs = RenderScript.create(this);
//RenderScript rs = MainActivity.rs;
ScriptC_yuv420888 mYuv420=new ScriptC_yuv420888 (rs);
// Y,U,V are defined as global allocations, the out-Allocation is the Bitmap.
// Note also that uAlloc and vAlloc are 1-dimensional while yAlloc is 2-dimensional.
Type.Builder typeUcharY = new Type.Builder(rs, Element.U8(rs));
//using safe height
typeUcharY.setX(yRowStride).setY(y.length / yRowStride);
Allocation yAlloc = Allocation.createTyped(rs, typeUcharY.create());
yAlloc.copyFrom(y);
mYuv420.set_ypsIn(yAlloc);
Type.Builder typeUcharUV = new Type.Builder(rs, Element.U8(rs));
// note that the size of the u's and v's are as follows:
// ( (width/2)*PixelStride + padding ) * (height/2)
// = (RowStride ) * (height/2)
// but I noted that on the S7 it is 1 less...
typeUcharUV.setX(u.length);
Allocation uAlloc = Allocation.createTyped(rs, typeUcharUV.create());
uAlloc.copyFrom(u);
mYuv420.set_uIn(uAlloc);
Allocation vAlloc = Allocation.createTyped(rs, typeUcharUV.create());
vAlloc.copyFrom(v);
mYuv420.set_vIn(vAlloc);
// handover parameters
mYuv420.set_picWidth(width);
mYuv420.set_uvRowStride (uvRowStride);
mYuv420.set_uvPixelStride (uvPixelStride);
Bitmap outBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Allocation outAlloc = Allocation.createFromBitmap(rs, outBitmap, Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_SCRIPT);
Script.LaunchOptions lo = new Script.LaunchOptions();
lo.setX(0, width); // by this we ignore the y’s padding zone, i.e. the right side of x between width and yRowStride
//using safe height
lo.setY(0, y.length / yRowStride);
mYuv420.forEach_doConvert(outAlloc,lo);
outAlloc.copyTo(outBitmap);
return outBitmap;
}在Nexus 7 (API 22)上进行测试,这会返回漂亮的彩色位图。然而,该设备具有微不足道的像素步幅(=1)和无填充(即rowstride=width)。在全新的三星S7 (API 23)上测试,我得到了颜色不正确的图片--除了绿色的。但这幅画并没有表现出对绿色的普遍偏见,似乎非绿色的颜色没有得到正确的再现。请注意,S7应用的u/v像素步长为2,没有填充。
由于最关键的代码行在rs-代码中,所以u/v平面uint uvIndex= (.)的访问我认为,这可能是个问题,可能是因为这里对像素的考虑不正确。有人看到解决办法了吗?谢谢。
更新:我检查了所有内容,并且非常肯定关于y,u,v的访问的代码是正确的。所以问题必须是u和v值本身。非绿色的颜色有一个紫色的倾斜,看看u,v值,他们似乎在一个相当狭窄的范围约110-150。我们真的有可能需要处理特定于设备的YUV -> RBG转换吗?!我错过什么了吗?
更新2:已经修正了代码,它现在工作了,多亏了Eddy的反馈。
发布于 2016-03-25 19:25:43
看
floor((float) uvPixelStride*(x)/2)它从Y坐标计算U,V行偏移量(uv_row_offset) .
如果uvPixelStride = 2,那么随着x的增加:
x = 0, uv_row_offset = 0
x = 1, uv_row_offset = 1
x = 2, uv_row_offset = 2
x = 3, uv_row_offset = 3这是不正确的。在uv_row_offset =1或3处没有有效的U/V像素值,因为uvPixelStride = 2。
你想要的
uvPixelStride * floor(x/2)(假设您不相信自己记得整数除法的关键整和行为,如果这样做的话):
uvPixelStride * (x/2)应该就够了
这样,您的映射就变成了:
x = 0, uv_row_offset = 0
x = 1, uv_row_offset = 0
x = 2, uv_row_offset = 2
x = 3, uv_row_offset = 2看看这是否修复了颜色错误。实际上,这里不正确的寻址意味着其他颜色样本都来自错误的颜色平面,因为潜在的YUV数据很可能是半平面的(因此U平面从V平面+1字节开始,两个平面相互交错)
发布于 2016-12-21 11:09:03
对于那些遇到错误的人
android.support.v8.renderscript.RSIllegalArgumentException: Array too small for allocation type使用buffer.capacity()而不是buffer.remaining()
如果您已经对图像进行了一些操作,则需要在缓冲区上调用rewind()方法。
发布于 2017-02-02 09:27:28
另外,对于其他人来说
android.support.v8.renderscript.RSIllegalArgumentException:数组太小,不适合分配类型
我通过将yAlloc.copyFrom(y);更改为yAlloc.copy1DRangeFrom(0, y.length, y);来修复它
https://stackoverflow.com/questions/36212904
复制相似问题