2017-11-26 50 views
0

카메라 2 미리보기에서 YUV_420_888 이미지를 비트 맵으로 변환하려고합니다. 그러나 출력 이미지의 색상이 올바르지 않습니다.YUV_420_888을 Android 카메라 2의 비트 맵으로 변환하는 잘못된 이미지

다음은 비트 맵을 생성하기 위해 실행중인 테스트 코드입니다. 테스트 코드 일 뿐이므로 비트 맵을 재활용하거나 RenderScript를 계속 작성하는 것과 같은 관련 요소에 대한 코드 검토를하지 마십시오. 이 코드는 YUV에서 RGB 로의 변환을 테스트하는 것입니다.

다른 요인은, 코드가있는 경우 필요한 경우에만 이전 안드로이드 버전으로 인해 적절한 부족으로 오래된 수동 변환을 사용하지 않고, 따라서 충분해야 RenderScript 특정 ScriptIntrinsicYuvToRGB를 사용하여 위의 API (22)에서 실행하기위한 것입니다 YUV_420_888 지원.

RenderScript는 모든 유형의 YUV 변환을 처리하기위한 전용 ScriptIntrinsicYuvToRGB를 이미 제공하고 있기 때문에 Image 객체에서 YUV 바이트 데이터를 가져 오는 방법에 문제가있을 수 있지만 문제가 어디에 있는지 파악할 수는 없다고 생각합니다. 입니다.

Android Studio에서 출력 비트 맵을 보려면 bitmap.recycle()에 중단 점을 배치하면 재활용되기 전에 "비트 맵보기"옵션을 사용하여 변수 디버그 창에서 볼 수 있습니다.

사람이 변환에 문제가 있는지 발견 할 수 있는지 알려 주시기 바랍니다 :

@Override 
public void onImageAvailable(ImageReader reader) 
{ 
    RenderScript rs = RenderScript.create(this.mContext); 

    final Image image = reader.acquireLatestImage(); 

    final Image.Plane[] planes = image.getPlanes(); 
    final ByteBuffer planeY = planes[0].getBuffer(); 
    final ByteBuffer planeU = planes[1].getBuffer(); 
    final ByteBuffer planeV = planes[2].getBuffer(); 

    // Get the YUV planes data 

    final int Yb = planeY.rewind().remaining(); 
    final int Ub = planeU.rewind().remaining(); 
    final int Vb = planeV.rewind().remaining(); 

    final ByteBuffer yuvData = ByteBuffer.allocateDirect(Yb + Ub + Vb); 

    planeY.get(yuvData.array(), 0, Yb); 
    planeU.get(yuvData.array(), Yb, Vb); 
    planeV.get(yuvData.array(), Yb + Vb, Ub); 

    // Initialize Renderscript 

    Type.Builder yuvType = new Type.Builder(rs, Element.YUV(rs)) 
      .setX(image.getWidth()) 
      .setY(image.getHeight()) 
      .setYuvFormat(ImageFormat.YUV_420_888); 

    final Type.Builder rgbaType = new Type.Builder(rs, Element.RGBA_8888(rs)) 
      .setX(image.getWidth()) 
      .setY(image.getHeight()); 

    Allocation yuvAllocation = Allocation.createTyped(rs, yuvType.create(), Allocation.USAGE_SCRIPT); 
    Allocation rgbAllocation = Allocation.createTyped(rs, rgbaType.create(), Allocation.USAGE_SCRIPT); 

    // Convert 

    yuvAllocation.copyFromUnchecked(yuvData.array()); 

    ScriptIntrinsicYuvToRGB scriptYuvToRgb = ScriptIntrinsicYuvToRGB.create(rs, Element.YUV(rs)); 
    scriptYuvToRgb.setInput(yuvAllocation); 
    scriptYuvToRgb.forEach(rgbAllocation); 

    // Get the bitmap 

    Bitmap bitmap = Bitmap.createBitmap(image.getWidth(), image.getHeight(), Bitmap.Config.ARGB_8888); 
    rgbAllocation.copyTo(bitmap); 

    // Release 

    bitmap.recycle(); 

    yuvAllocation.destroy(); 
    rgbAllocation.destroy(); 
    rs.destroy(); 

    image.close(); 
} 
+0

일반적으로 U 및 V 평면은 겹칩니다. YUV_420_888은 NV21 이미지를 호스팅 할 수있는 래퍼입니다 (예 : NV21 이미지). 이 경우 planeU 및 planeV 버퍼의 길이는 실제 크기의 두 배가됩니다. Renderscript를 사용하여 YUV_420_888을 Bitmap으로 변환하는 코드는 [here] (https://stackoverflow.com/questions/36212904/yuv-420-888-interpretation-on-samsung-galaxy-s7-camera2)에서 찾을 수 있습니다. –

+0

안녕하세요. 힌트를 가져 주셔서 감사합니다. 새로운 YUV 변환 스크립트를 만드는 것을 암시하는 대안을 사용하는 대신 기존 ScriptIntrinsicYuvToRGB를 사용해야한다는 질문을보다 명확하게 지정하도록 업데이트했습니다. 감사합니다. – PerracoLabs

+0

** ScriptIntrinsicYuvToRGB **를 사용하기 위해 귀하의 선호도를 존중하지만, 맞춤형 렌더 스크립트를 사용하는 것이 덜 효율적이지는 않습니다. –

답변

0

내 자신의 질문에 답하면 실제 문제는 이미지 평면을 ByteBuffer로 변환하는 방법에서 의심 스러웠습니다. 다음 솔루션은 NV21 & YV12 모두에서 작동합니다. YUV 데이터는 이미 별개의 평면으로 제공되므로 행과 픽셀 스트라이드를 기반으로 올바른 방법으로 가져 오는 것입니다. 또한 데이터가 RenderScript 내장 함수에 전달되는 방법에 약간의 수정이 필요했습니다.

@Override 
public void onImageAvailable(ImageReader reader) 
{ 
    // Get the YUV data 

    final Image image = reader.acquireLatestImage(); 
    final ByteBuffer yuvBytes = this.imageToByteBuffer(image); 

    // Convert YUV to RGB 

    final RenderScript rs = RenderScript.create(this.mContext); 

    final Bitmap  bitmap  = Bitmap.createBitmap(image.getWidth(), image.getHeight(), Bitmap.Config.ARGB_8888); 
    final Allocation allocationRgb = Allocation.createFromBitmap(rs, bitmap); 

    final Allocation allocationYuv = Allocation.createSized(rs, Element.U8(rs), yuvBytes.array().length); 
    allocationYuv.copyFrom(yuvBytes); 

    scriptYuvToRgb = ScriptIntrinsicYuvToRGB.create(rs, Element.U8_4(rs)); 
    scriptYuvToRgb.setInput(allocationYuv); 
    scriptYuvToRgb.forEach(allocationRgb); 

    allocationRgb.copyTo(bitmap); 

    // Release 

    bitmap.recycle(); 

    yuvAllocation.destroy(); 
    rgbAllocation.destroy(); 
    rs.destroy(); 

    image.close(); 
} 

private ByteBuffer imageToByteBuffer(final Image image) 
{ 
    final Rect crop = image.getCropRect(); 
    final int width = crop.width(); 
    final int height = crop.height(); 

    final Image.Plane[] planes  = image.getPlanes(); 
    final byte[]  rowData = new byte[planes[0].getRowStride()]; 
    final int   bufferSize = size.getWidth() * size.getHeight() * ImageFormat.getBitsPerPixel(ImageFormat.YUV_420_888)/8; 
    final ByteBuffer output  = ByteBuffer.allocateDirect(bufferSize); 

    int channelOffset = 0; 
    int outputStride = 0; 

    for (int planeIndex = 0; planeIndex < 3; planeIndex++) 
    { 
     if (planeIndex == 0) 
     { 
      channelOffset = 0; 
      outputStride = 1; 
     } 
     else if (planeIndex == 1) 
     { 
      channelOffset = width * height + 1; 
      outputStride = 2; 
     } 
     else if (planeIndex == 2) 
     { 
      channelOffset = width * height; 
      outputStride = 2; 
     } 

     final ByteBuffer buffer  = planes[planeIndex].getBuffer(); 
     final int  rowStride = planes[planeIndex].getRowStride(); 
     final int  pixelStride = planes[planeIndex].getPixelStride(); 

     final int shift   = (planeIndex == 0) ? 0 : 1; 
     final int widthShifted = width >> shift; 
     final int heightShifted = height >> shift; 

     buffer.position(rowStride * (crop.top >> shift) + pixelStride * (crop.left >> shift)); 

     for (int row = 0; row < heightShifted; row++) 
     { 
      final int length; 

      if (pixelStride == 1 && outputStride == 1) 
      { 
       length = widthShifted; 
       buffer.get(output.array(), channelOffset, length); 
       channelOffset += length; 
      } 
      else 
      { 
       length = (widthShifted - 1) * pixelStride + 1; 
       buffer.get(rowData, 0, length); 

       for (int col = 0; col < widthShifted; col++) 
       { 
        output.array()[channelOffset] = rowData[col * pixelStride]; 
        channelOffset += outputStride; 
       } 
      } 

      if (row < heightShifted - 1) 
      { 
       buffer.position(buffer.position() + rowStride - length); 
      } 
     } 
    } 

    return output; 
} 
+0

이 방법은 상당한 오버 헤드를 유발합니다. 카메라로부터의 DirectByteBuffer의 특성에 의해, 픽셀 단위의 카피는, Java 네이티브 메소드 (JNI를 사용)로서 기술하면 (자) 훨씬 효율적입니다. 하지만 더 중요한 것은 입력을 NV21 또는 YV12로 식별 할 수있는 경우 입력을 한 번에 복사 할 수 있습니다. https://software.intel.com/en-us/articles/multi-stage-post-processing-renderscript- for-android-on-intel-architecture –

+0

절대적으로 동의합니다. 오버 헤드가 있으며, 필자가 내 대답에서 제공 한 샘플 코드는 프로덕션을위한 것이 아니라 테스트 용으로 만 최적화되었습니다.내 솔루션을 따르는 경우 적절한 구현, 별도의 버퍼, 아마도 FIFO 한 다음 이미지 미리보기를 진행할 수있는 카메라 이미지 콜백 및 실제 변환/백그라운드 스레드에서 처리를 수행 할 수 있도록 이미지 데이터를 복사하는 것입니다. , 그리고 내 샘플 코드의 워크 플로와는 다릅니다. – PerracoLabs

+0

귀하의 의견에 동의 할 수 없을지 모르겠습니다. 실시간 미리보기는 ** onImageAvailable() **과 독립적입니다. 또한, ** camera2 **는 이미 콜백을 백그라운드 스레드에 넣으므로 다른 것을 도입 할 필요가 없습니다. 처리 속도가 프레임 속도보다 느리다면, FIFO보다는 오히려 따라 잡기 위해 이전 프레임을 버리는 것이 좋습니다. –

0

는 RS 할당으로의 YUV_420_888 카메라 프레임을 복사 할 수는 없습니다. 사실, 오늘 현재로, Renderscript does not support this format.

프레임 아래에 프레임이 NV21 또는 YV12 인 경우 전체 ByteBuffer를 배열에 복사하고 RS 할당으로 전달할 수 있습니다.