Created
April 1, 2022 06:03
-
-
Save mattbell87/ddc7445c34df64ab964963fd7f2ce763 to your computer and use it in GitHub Desktop.
Port of ImageUtil.kt from ONNX Runtime Android Example to Java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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