diff options
author | Andrew Harp <andrewharp@google.com> | 2017-05-04 12:48:11 -0800 |
---|---|---|
committer | TensorFlower Gardener <gardener@tensorflow.org> | 2017-05-04 14:04:47 -0700 |
commit | fd69bb292af7f15cd364e36ead7f596a3c484b2c (patch) | |
tree | f12e818767035d160c91f1394b5d9d83bdc9d151 | |
parent | dd140f79e06a81c52cd8fc9ec6cda975a78a401f (diff) |
Android demo: Add YUV -> RGB conversion Java implementation as fallback if native implementation is not found. This means that compiling libtensorflow_demo.so will only be strictly necessary for the Detection example (which uses native object tracking). A followup change will add graceful degradation in that case too.
Java conversion may be slower depending on the device, but should still be acceptable for demo purposes as the majority of the compute time will still be spent on TF inference passes.
Note that this has no effect on the necessity of libtensorflow_inference.so, which provides the actual TF support. However libtensorflow_inference.so may be added to applications via the prebuilt AAR, so no native compilation is necessary.
Partially addresses #6385
Change: 155121431
6 files changed, 89 insertions, 21 deletions
diff --git a/tensorflow/examples/android/src/org/tensorflow/demo/ClassifierActivity.java b/tensorflow/examples/android/src/org/tensorflow/demo/ClassifierActivity.java index b26a231678..bc39126925 100644 --- a/tensorflow/examples/android/src/org/tensorflow/demo/ClassifierActivity.java +++ b/tensorflow/examples/android/src/org/tensorflow/demo/ClassifierActivity.java @@ -194,13 +194,12 @@ public class ClassifierActivity extends CameraActivity implements OnImageAvailab yuvBytes[0], yuvBytes[1], yuvBytes[2], - rgbBytes, previewWidth, previewHeight, yRowStride, uvRowStride, uvPixelStride, - false); + rgbBytes); image.close(); } catch (final Exception e) { diff --git a/tensorflow/examples/android/src/org/tensorflow/demo/DetectorActivity.java b/tensorflow/examples/android/src/org/tensorflow/demo/DetectorActivity.java index 206a99f3e3..cdb6c3fed8 100644 --- a/tensorflow/examples/android/src/org/tensorflow/demo/DetectorActivity.java +++ b/tensorflow/examples/android/src/org/tensorflow/demo/DetectorActivity.java @@ -273,13 +273,12 @@ public class DetectorActivity extends CameraActivity implements OnImageAvailable yuvBytes[0], yuvBytes[1], yuvBytes[2], - rgbBytes, previewWidth, previewHeight, yRowStride, uvRowStride, uvPixelStride, - false); + rgbBytes); image.close(); } catch (final Exception e) { diff --git a/tensorflow/examples/android/src/org/tensorflow/demo/StylizeActivity.java b/tensorflow/examples/android/src/org/tensorflow/demo/StylizeActivity.java index 7634be5c02..7afe2bf541 100644 --- a/tensorflow/examples/android/src/org/tensorflow/demo/StylizeActivity.java +++ b/tensorflow/examples/android/src/org/tensorflow/demo/StylizeActivity.java @@ -65,10 +65,6 @@ import org.tensorflow.demo.R; * Artistic Style" (https://arxiv.org/abs/1610.07629) */ public class StylizeActivity extends CameraActivity implements OnImageAvailableListener { - static { - System.loadLibrary("tensorflow_demo"); - } - private static final Logger LOGGER = new Logger(); private static final String MODEL_FILE = "file:///android_asset/stylize_quantized.pb"; @@ -509,17 +505,17 @@ public class StylizeActivity extends CameraActivity implements OnImageAvailableL final int yRowStride = planes[0].getRowStride(); final int uvRowStride = planes[1].getRowStride(); final int uvPixelStride = planes[1].getPixelStride(); + ImageUtils.convertYUV420ToARGB8888( yuvBytes[0], yuvBytes[1], yuvBytes[2], - rgbBytes, previewWidth, previewHeight, yRowStride, uvRowStride, uvPixelStride, - false); + rgbBytes); image.close(); } catch (final Exception e) { diff --git a/tensorflow/examples/android/src/org/tensorflow/demo/TensorFlowMultiBoxDetector.java b/tensorflow/examples/android/src/org/tensorflow/demo/TensorFlowMultiBoxDetector.java index f3e7114335..1dcf9f55ef 100644 --- a/tensorflow/examples/android/src/org/tensorflow/demo/TensorFlowMultiBoxDetector.java +++ b/tensorflow/examples/android/src/org/tensorflow/demo/TensorFlowMultiBoxDetector.java @@ -41,10 +41,6 @@ import org.tensorflow.demo.env.Logger; public class TensorFlowMultiBoxDetector implements Classifier { private static final Logger LOGGER = new Logger(); - static { - System.loadLibrary("tensorflow_demo"); - } - // Only return this many results with at least this confidence. private static final int MAX_RESULTS = Integer.MAX_VALUE; diff --git a/tensorflow/examples/android/src/org/tensorflow/demo/TensorFlowYoloDetector.java b/tensorflow/examples/android/src/org/tensorflow/demo/TensorFlowYoloDetector.java index 174723071d..b7e36a2379 100644 --- a/tensorflow/examples/android/src/org/tensorflow/demo/TensorFlowYoloDetector.java +++ b/tensorflow/examples/android/src/org/tensorflow/demo/TensorFlowYoloDetector.java @@ -31,10 +31,6 @@ import org.tensorflow.demo.env.SplitTimer; public class TensorFlowYoloDetector implements Classifier { private static final Logger LOGGER = new Logger(); - static { - System.loadLibrary("tensorflow_demo"); - } - // Only return this many results with at least this confidence. private static final int MAX_RESULTS = 5; diff --git a/tensorflow/examples/android/src/org/tensorflow/demo/env/ImageUtils.java b/tensorflow/examples/android/src/org/tensorflow/demo/env/ImageUtils.java index db929e5e08..5f2ff9164c 100644 --- a/tensorflow/examples/android/src/org/tensorflow/demo/env/ImageUtils.java +++ b/tensorflow/examples/android/src/org/tensorflow/demo/env/ImageUtils.java @@ -27,6 +27,14 @@ import java.io.FileOutputStream; public class ImageUtils { @SuppressWarnings("unused") private static final Logger LOGGER = new Logger(); + + static { + try { + System.loadLibrary("tensorflow_demo"); + } catch (UnsatisfiedLinkError e) { + LOGGER.w("Native library not found, native RGB -> YUV conversion may be unavailable."); + } + } /** * Utility method to compute the allocated size in bytes of a YUV420SP image @@ -83,10 +91,84 @@ public class ImageUtils { } } + // This value is 2 ^ 18 - 1, and is used to clamp the RGB values before their ranges + // are normalized to eight bits. + static final int kMaxChannelValue = 262143; + + // Always prefer the native implementation if available. + private static boolean useNativeConversion = true; + + public static void convertYUV420ToARGB8888( + byte[] yData, + byte[] uData, + byte[] vData, + int width, + int height, + int yRowStride, + int uvRowStride, + int uvPixelStride, + int[] out) { + if (useNativeConversion) { + try { + convertYUV420ToARGB8888( + yData, uData, vData, out, width, height, yRowStride, uvRowStride, uvPixelStride, false); + return; + } catch (UnsatisfiedLinkError e) { + LOGGER.w("Native YUV -> RGB implementation not found, falling back to Java implementation"); + useNativeConversion = false; + } + } + + int i = 0; + for (int y = 0; y < height; y++) { + int pY = yRowStride * y; + int uv_row_start = uvRowStride * (y >> 1); + int pUV = uv_row_start; + int pV = uv_row_start; + + for (int x = 0; x < width; x++) { + int uv_offset = pUV + (x >> 1) * uvPixelStride; + out[i++] = + YUV2RGB( + convertByteToInt(yData, pY + x), + convertByteToInt(uData, uv_offset), + convertByteToInt(vData, uv_offset)); + } + } + } + + private static int convertByteToInt(byte[] arr, int pos) { + return arr[pos] & 0xFF; + } + + private static int YUV2RGB(int nY, int nU, int nV) { + nY -= 16; + nU -= 128; + nV -= 128; + if (nY < 0) nY = 0; + + // This is the floating point equivalent. We do the conversion in integer + // because some Android devices do not have floating point in hardware. + // nR = (int)(1.164 * nY + 2.018 * nU); + // nG = (int)(1.164 * nY - 0.813 * nV - 0.391 * nU); + // nB = (int)(1.164 * nY + 1.596 * nV); + + final int foo = 1192 * nY; + int nR = foo + 1634 * nV; + int nG = foo - 833 * nV - 400 * nU; + int nB = foo + 2066 * nU; + + nR = Math.min(kMaxChannelValue, Math.max(0, nR)); + nG = Math.min(kMaxChannelValue, Math.max(0, nG)); + nB = Math.min(kMaxChannelValue, Math.max(0, nB)); + + return 0xff000000 | ((nR << 6) & 0x00ff0000) | ((nG >> 2) & 0x0000FF00) | ((nB >> 10) & 0xff); + } + /** - * Converts YUV420 semi-planar data to ARGB 8888 data using the supplied width - * and height. The input and output must already be allocated and non-null. - * For efficiency, no error checking is performed. + * Converts YUV420 semi-planar data to ARGB 8888 data using the supplied width and height. The + * input and output must already be allocated and non-null. For efficiency, no error checking is + * performed. * * @param input The array of YUV 4:2:0 input data. * @param output A pre-allocated array for the ARGB 8:8:8:8 output data. |