Skip to content

Instantly share code, notes, and snippets.

@mattbell87
Created April 1, 2022 06:03
Show Gist options
  • Save mattbell87/ddc7445c34df64ab964963fd7f2ce763 to your computer and use it in GitHub Desktop.
Save mattbell87/ddc7445c34df64ab964963fd7f2ce763 to your computer and use it in GitHub Desktop.
Port of ImageUtil.kt from ONNX Runtime Android Example to Java
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Source: https://github.com/microsoft/onnxruntime-inference-examples/blob/main/mobile/examples/image_classification/android/app/src/main/java/ai/onnxruntime/example/imageclassifier/ImageUtil.kt
*/
package com.yourpackagename;
import android.graphics.*;
import androidx.camera.core.ImageProxy;
import java.io.ByteArrayOutputStream;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
public class ImageUtils {
final static int DIM_BATCH_SIZE = 1;
final static int DIM_PIXEL_SIZE = 3;
final static int IMAGE_SIZE_X = 224;
final static int IMAGE_SIZE_Y = 224;
public static FloatBuffer PreProcess(Bitmap bitmap) {
FloatBuffer imgData = FloatBuffer.allocate(
DIM_BATCH_SIZE
* DIM_PIXEL_SIZE
* IMAGE_SIZE_X
* IMAGE_SIZE_Y
);
imgData.rewind();
int stride = IMAGE_SIZE_X * IMAGE_SIZE_Y;
int[] bmpData = new int[stride];
bitmap.getPixels(bmpData, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(), bitmap.getHeight());
for (int i = 0; i < IMAGE_SIZE_X; i++) {
for (int j = 0; j < IMAGE_SIZE_Y; j++) {
int idx = IMAGE_SIZE_Y * i + j;
int pixelValue = bmpData[idx];
imgData.put(idx, (((pixelValue >> 16 & 0xFF) / 255f - 0.485f) / 0.229f));
imgData.put(idx + stride, (((pixelValue >> 8 & 0xFF) / 255f - 0.456f) / 0.224f));
imgData.put(idx + stride * 2, (((pixelValue & 0xFF) / 255f - 0.406f) / 0.225f));
}
}
imgData.rewind();
return imgData;
}
public static Bitmap ImageToBitmap(ImageProxy image) {
byte[] nv21 = Yuv420888ToNv21(image);
YuvImage yuv = new YuvImage(nv21, ImageFormat.NV21, image.getWidth(), image.getHeight(), null);
return YuvImageToBitmap(yuv);
}
public static Bitmap YuvImageToBitmap(YuvImage yuvImage) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
if (!yuvImage.compressToJpeg(new Rect(0, 0, yuvImage.getWidth(), yuvImage.getHeight()), 100, out))
return null;
byte[] imageBytes = out.toByteArray();
return BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length);
}
public static byte[] Yuv420888ToNv21(ImageProxy image) {
int pixelCount = image.getCropRect().width() * image.getCropRect().height();
int pixelSizeBits = ImageFormat.getBitsPerPixel(ImageFormat.YUV_420_888);
return ImageToByteBuffer(image, new byte[pixelCount * pixelSizeBits / 8], pixelCount);
}
public static byte[] ImageToByteBuffer(ImageProxy image, byte[] outputBuffer, int pixelCount) {
assert(image.getFormat() == ImageFormat.YUV_420_888);
Rect imageCrop = image.getCropRect();
ImageProxy.PlaneProxy[] imagePlanes = image.getPlanes();
for (int planeIndex = 0; planeIndex < imagePlanes.length; planeIndex++) {
ImageProxy.PlaneProxy plane = imagePlanes[planeIndex];
// How many values are read in input for each output value written
// Only the Y plane has a value for every pixel, U and V have half the resolution i.e.
//
// Y Plane U Plane V Plane
// =============== ======= =======
// Y Y Y Y Y Y Y Y U U U U V V V V
// Y Y Y Y Y Y Y Y U U U U V V V V
// Y Y Y Y Y Y Y Y U U U U V V V V
// Y Y Y Y Y Y Y Y U U U U V V V V
// Y Y Y Y Y Y Y Y
// Y Y Y Y Y Y Y Y
// Y Y Y Y Y Y Y Y
int outputStride;
// The index in the output buffer the next value will be written at
// For Y it's zero, for U and V we start at the end of Y and interleave them i.e.
//
// First chunk Second chunk
// =============== ===============
// Y Y Y Y Y Y Y Y V U V U V U V U
// Y Y Y Y Y Y Y Y V U V U V U V U
// Y Y Y Y Y Y Y Y V U V U V U V U
// Y Y Y Y Y Y Y Y V U V U V U V U
// Y Y Y Y Y Y Y Y
// Y Y Y Y Y Y Y Y
// Y Y Y Y Y Y Y Y
int outputOffset;
switch (planeIndex) {
case 0:
outputStride = 1;
outputOffset = 0;
break;
case 1:
outputStride = 2;
// For NV21 format, U is in odd-numbered indices
outputOffset = pixelCount + 1;
break;
case 2:
outputStride = 2;
// For NV21 format, V is in even-numbered indices
outputOffset = pixelCount;
break;
default:
// Image contains more than 3 planes, something strange is going on
continue;
}
ByteBuffer planeBuffer = plane.getBuffer();
int rowStride = plane.getRowStride();
int pixelStride = plane.getPixelStride();
Rect planeCrop;
if (planeIndex == 0) {
planeCrop = imageCrop;
} else {
planeCrop = new Rect(
imageCrop.left / 2,
imageCrop.top / 2,
imageCrop.right / 2,
imageCrop.bottom / 2
);
}
int planeWidth = planeCrop.width();
int planeHeight = planeCrop.height();
// Intermediate buffer used to store the bytes of each row
byte[] rowBuffer = new byte[plane.getRowStride()];
// Size of each row in bytes
int rowLength = (pixelStride == 1 && outputStride == 1) ?
planeWidth
:
// Take into account that the stride may include data from pixels other than this
// particular plane and row, and that could be between pixels and not after every
// pixel:
//
// |---- Pixel stride ----| Row ends here --> |
// | Pixel 1 | Other Data | Pixel 2 | Other Data | ... | Pixel N |
//
// We need to get (N-1) * (pixel stride bytes) per row + 1 byte for the last pixel
(planeWidth - 1) * pixelStride + 1;
for (int row = 0; row < planeHeight; row++) {
// Move buffer position to the beginning of this row
planeBuffer.position(
(row + planeCrop.top) * rowStride + planeCrop.left * pixelStride
);
if (pixelStride == 1 && outputStride == 1) {
// When there is a single stride value for pixel and output, we can just copy
// the entire row in a single step
planeBuffer.get(outputBuffer, outputOffset, rowLength);
outputOffset += rowLength;
} else {
// When either pixel or output have a stride > 1 we must copy pixel by pixel
planeBuffer.get(rowBuffer, 0, rowLength);
for (int col = 0; col < planeHeight; col++) {
outputBuffer[outputOffset] = rowBuffer[col * pixelStride];
outputOffset += outputStride;
}
}
}
}
return outputBuffer;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment