-
-
Notifications
You must be signed in to change notification settings - Fork 268
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
CameraImage conversion returns red Image #669
Comments
You mentioned before the camera image is in yuv420 colors, which isn't the same as bgra colors you're telling Image. Perhaps you can try converting the yuv420 colors, like in #535 (comment). |
@brendan-duncan Thank you for the reply. However, all the solutions discussed in #535 (comment) don't work. I got an Index Range error. If possible could you try and modify the capturejpeg function in the main.dart code in the provided repository https://github.com/collo54/vision_edit/blob/d0c9d11d81c8150de5d00e2437101ea1bf382da2/lib/main.dart#L143 to show the fix. |
I can certainly help but I have very little experience with Flutter, so I can offer suggestions but I'm not set up to do any testing to see if any of this works at the moment. I wish I had some more time. CameraImage has a The format can be one of the ones defined in https://pub.dev/documentation/camera/latest/camera/ImageFormatGroup.html. So the full function to convert a CameraImage to an img.Image would need to be based on the format. I believe on iOS the CameraImage format is bgra888, and on Android it will be yuv420, which will need additional conversion like the #535 issue suggests. For the image you posted here, my suspicion is you're testing on iOS and the format of your CameraImage is bgra8888. That makes sense since the images you're showing look intact, they're just red. So why is it red? You are setting the numChannels of the Image to 1, so the image will only have a red channel. If you set numChannels to 4, then it would be an rgba image. Then, specifying the Image channel order as img.ChannelOrder.bgra, it should copy the bytes in the correct order. Something you might possibly run into is a shifting of the pixels. If you don't notice that, you can skip the following. There was another bug reported a while back related to bgra8888 CameraImage, #599. With that bug, they converted the CameraImage to an Image similar to what you are trying to do. Their code was CameraImage image; // from cameraController.startImageStream((image) { ... });
final img = imglib.Image.fromBytes(
width: image.width,
height: image.height,
bytes: image.planes.first.bytes.buffer,
rowStride: image.planes.first.bytesPerRow,
numChannels: 4,
order: imglib.ChannelOrder.bgra,
); The issue they had reported was that the pixels are shifted. It turns out there seems to be some sort of header with the CameraImage bytes. So if the above code works for you but the pixels seem offset, you can try their suggestion, CameraImage image; // from cameraController.startImageStream((image) { ... });
final img = imglib.Image.fromBytes(
width: image.width,
height: image.height,
bytes: image.planes.first.bytes.buffer,
byteOffset: 28, // <---- offset the buffer bytes
rowStride: image.planes.first.bytesPerRow,
numChannels: 4,
order: imglib.ChannelOrder.bgra,
); |
@brendan-duncan The CameraImage format is yuv420. I am testing on android. The code provided above #669 (comment) doesn't work. When I change the numChannels to 4 I get a new error shown below. Only when I use I/flutter ( 6008): Bad state: Too few elements
E/FlutterJNI( 6008): Failed to decode image
E/FlutterJNI( 6008): android.graphics.ImageDecoder$DecodeException: Failed to create image decoder with message 'unimplemented'Input contained an error. I suppose its all boils down to "Android it will be yuv420, which will need additional conversion like the #535 issue suggests" as you said. If you have updated info on how to do this I am sure it will work.Thank you. |
I've found a partial solution. Partial because the solution is slow so a different isolate should be configured to handle the process. First follow the solution in the link
CameraImage availableImage;
int imageWidth = availableImage.width;
int imageHeight = availableImage.height;
int imageStride = availableImage.planes[0].bytesPerRow;
List<Uint8List> planes = [];
for (int planeIndex = 0; planeIndex < 3; planeIndex++) {
Uint8List buffer;
int width;
int height;
if (planeIndex == 0) {
width = availableImage.width;
height = availableImage.height;
} else {
width = availableImage.width ~/ 2;
height = availableImage.height ~/ 2;
}
buffer = Uint8List(width * height);
int pixelStride = availableImage.planes[planeIndex].bytesPerPixel!;
int rowStride = availableImage.planes[planeIndex].bytesPerRow;
int index = 0;
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
buffer[index++] = availableImage
.planes[planeIndex].bytes[i * rowStride + j * pixelStride];
}
}
planes.add(buffer);
}
Uint8List yuv420ToRgba8888(List<Uint8List> planes, int width, int height) {
final yPlane = planes[0];
final uPlane = planes[1];
final vPlane = planes[2];
final Uint8List rgbaBytes = Uint8List(width * height * 4);
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
final int yIndex = y * width + x;
final int uvIndex = (y ~/ 2) * (width ~/ 2) + (x ~/ 2);
final int yValue = yPlane[yIndex] & 0xFF;
final int uValue = uPlane[uvIndex] & 0xFF;
final int vValue = vPlane[uvIndex] & 0xFF;
final int r = (yValue + 1.13983 * (vValue - 128)).round().clamp(0, 255);
final int g =
(yValue - 0.39465 * (uValue - 128) - 0.58060 * (vValue - 128))
.round()
.clamp(0, 255);
final int b = (yValue + 2.03211 * (uValue - 128)).round().clamp(0, 255);
final int rgbaIndex = yIndex * 4;
rgbaBytes[rgbaIndex] = r.toUnsigned(8);
rgbaBytes[rgbaIndex + 1] = g.toUnsigned(8);
rgbaBytes[rgbaIndex + 2] = b.toUnsigned(8);
rgbaBytes[rgbaIndex + 3] = 255; // Alpha value
}
}
return rgbaBytes;
}
Uint8List data = yuv420ToRgba8888(planes, imageWidth, imageHeight);
import 'dart:ui' as ui;
Future<ui.Image> createImage(
Uint8List buffer, int width, int height, ui.PixelFormat pixelFormat) {
final Completer<ui.Image> completer = Completer();
ui.decodeImageFromPixels(buffer, width, height, pixelFormat, (ui.Image img) {
completer.complete(img);
});
return completer.future;
}
Future<Uint8List> convertFlutterUiToImage(ui.Image uiImage) async {
final uiBytes = await uiImage.toByteData();
if (uiBytes == null) {
throw Exception('Failed to convert UI image to ByteData');
}
final image = img.Image.fromBytes(
width: uiImage.width,
height: uiImage.height,
bytes: uiBytes.buffer,
numChannels: 4,
);
final rotatedImage = img.copyRotate(image, angle: 90);
final uint8list = Uint8List.fromList(img.encodeJpg(
rotatedImage,
chroma: img.JpegChroma.yuv420,
));
return uint8list;
}
} The Uint8List can then be rendered using Flutter widgets like Image.memory Image.memory(uint8Listdata); @brendan-duncan Thank you for the leads provided. Cheers. |
Seems to me you should be able to combine all those steps into a single step. This should work for yuv420 format CameraImage's. bgra8888 would be converted as described above. CameraImage fromImage;
int width = fromImage.width;
int height = fromImage.height;
int halfWidth = width ~/ 2;
int halfHeight = height ~/ 2;
int pixelStride0 = fromImage.planes[0].bytesPerPixel!;
int rowStride0 = fromImage.planes[0].bytesPerRow;
int pixelStride1 = fromImage.planes[0].bytesPerPixel!;
int rowStride1 = fromImage.planes[0].bytesPerRow;
// img.Image will have a default numChannels of 3, RGB. Camera images won't have transparency so an alpha isn't necessary.
img.Image toImage = img.Image(width: width, height: height);
// Get a Pixel iterator for the first pixel
var toPixel = toImage.getPixel(0, 0);
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
int y = fromImage.planes[0].bytes[i * rowStride0 + j * pixelStride0];
int u = fromImage.planes[1].bytes[i * rowStride1 + j * pixelStride1];
int v = fromImage.planes[2].bytes[i * rowStride1 + j * pixelStride1];
final int r = (y + 1.13983 * (v - 128)).round().clamp(0, 255);
final int g = (y - 0.39465 * (u - 128) - 0.58060 * (v - 128)).round().clamp(0, 255);
final int b = (y + 2.03211 * (u - 128)).round().clamp(0, 255);
toPixel.setRgb(r, g, b);
// Go to the next pixel in the toImage
toPixel.moveNext();
}
} |
I tried this to stream the imagedata over websocket, the data transmits well but there is a lag of few milliseconds,. |
https://github.com/brendan-duncan/image/issues/654#issuecomment-2270202591
When converting CameraImage object from camera package using the Image package I get a red image instead of the correct image color as shown below (expected vs red error image). Code below image.
The text was updated successfully, but these errors were encountered: