I'm converting a single pixel of NV21 to RGB, it is what I did:
public int getYUVvalue(byte[] yuv,int width,int height,int x,int y){
int total=width*height;
int Y=(0xff&yuv[y*width+x])-16;
int U=(0xff&yuv[(y/2)*(width/2)+(x/2)+total])-128;
int V=(0xff&yuv[(y/2)*(width/2)+(x/2)+total+1])-128;
return this.convertYUVtoRGB(Y, U, V);
}
private static int convertYUVtoRGB(int y, int u, int v) {
int y1192 = 1192 * y;
int r = (y1192 + 1634 * v);
int g = (y1192 - 833 * v - 400 * u);
int b = (y1192 + 2066 * u);
if (r < 0) r = 0; else if (r > 262143) r = 262143;
if (g < 0) g = 0; else if (g > 262143) g = 262143;
if (b < 0) b = 0; else if (b > 262143) b = 262143;
return 0xff000000 | ((r << 6) & 0xff0000) | ((g >> 2) & 0xff00) | ((b >> 10) & 0xff);
}
but the result is just black and white, looks like the U and V value are not correct, can someone help me with this issue?
thank you!
update: I confirm my Y value is right, so I think the problem is at the U and V value, which I have no clue where the problem is.
update#2: My code is very similar to decodeYUV420sp(), which is a widely used function to convert the whole image, it works perfectly, mine is just to convert a single pixel, still trying to figure out the problem by comparing it with my code. Here is the decodeyuv420sp function:
static public void decodeYUV420SP(int[] rgb, byte[] yuv420sp, int width, int height) {
final int frameSize = width * height;
for (int j = 0, yp = 0; j < height; j++) {
int uvp = frameSize + (j >> 1) * width, u = 0, v = 0;
for (int i = 0; i < width; i++, yp++) {
int y = (0xff & ((int) yuv420sp[yp])) - 16;
if (y < 0) y = 0;
if ((i & 1) == 0) {
v = (0xff & yuv420sp[uvp++]) - 128;
u = (0xff & yuv420sp[uvp++]) - 128;
}
int y1192 = 1192 * y;
int r = (y1192 + 1634 * v);
int g = (y1192 - 833 * v - 400 * u);
int b = (y1192 + 2066 * u);
if (r < 0) r = 0; else if (r > 262143) r = 262143;
if (g < 0) g = 0; else if (g > 262143) g = 262143;
if (b < 0) b = 0; else if (b > 262143) b = 262143;
rgb[yp] = 0xff000000 | ((r << 6) & 0xff0000) | ((g >> 2) & 0xff00) | ((b >> 10) & 0xff);
}
}
}
I still can't see where my mistake is.
update#3: problem solved, thanks for your help everyone!
The logic for the array indexes of U & V in the getYUVvalue function does not match the logic in decodeYUV420SP. (I count 3 differences.) Without a sample byte array in this format to test with, I can't be sure, but I think it should be:
int U=(0xff&yuv[(y/2)*width+(x&~1)+total+1])-128;
int V=(0xff&yuv[(y/2)*width+(x&~1)+total])-128;
It's missing the if (y < 0) y = 0; check also. Maybe that matters.
Related
I have the following code:
private static int pixelDiff(int rgb1, int rgb2) {
int r1 = (rgb1 >> 16) & 0xff;
int g1 = (rgb1 >> 8) & 0xff;
int b1 = rgb1 & 0xff;
int r2 = (rgb2 >> 16) & 0xff;
int g2 = (rgb2 >> 8) & 0xff;
int b2 = rgb2 & 0xff;
return Math.abs(r1 - r2) + Math.abs(g1 - g2) + Math.abs(b1 - b2);
}
and it works without a problem, but it takes to long and i don't know how it optimize it.
So the basic is, that i want to compare two images and get the percentage of difference.
Therefor I load the RGB of both images and compare them with this code.
My question: Is it possible to optimize this code, or do you have any idea to compare two images(not only that they are equal)
UPDATE:
here is the full code:
private double getDifferencePercent(BufferedImage img1, BufferedImage img2) {
int width = img1.getWidth();
int height = img1.getHeight();
int width2 = img2.getWidth();
int height2 = img2.getHeight();
if (width != width2 || height != height2) {
throw new IllegalArgumentException(String.format("Images must have the same dimensions: (%d,%d) vs. (%d,%d)", width, height, width2, height2));
}
long diff = 0;
for (int y = height - 1; y >= 0; y--) {
for (int x = width - 1; x >= 0; x--) {
diff += pixelDiff(img1.getRGB(x, y), img2.getRGB(x, y));
}
}
long maxDiff = 765L * width * height;
return 100.0 * diff / maxDiff;
}
private static int pixelDiff(int rgb1, int rgb2) {
int r1 = (rgb1 >> 16) & 0xff;
int g1 = (rgb1 >> 8) & 0xff;
int b1 = rgb1 & 0xff;
int r2 = (rgb2 >> 16) & 0xff;
int g2 = (rgb2 >> 8) & 0xff;
int b2 = rgb2 & 0xff;
return Math.abs(r1 - r2) + Math.abs(g1 - g2) + Math.abs(b1 - b2);
}
I checked this with a profiler and it shows that pixelDiff() is very slow.
Currently I'm building an app to do real-time image processing and then display. The first step is to try displaying the original preview using Camera2 API and ANativeWindow API. I pass the y, u, v channels through JNI separately and do YUV2RGB conversion following the Wikipedia article, but got wrong color output running on Google Pixel - 7.1.0 - API 25 - 1080x1920 on Genymotion:
Implementation of ImageReader.OnImageAvailableListener :
private ImageReader.OnImageAvailableListener mOnImageAvailableListener = new ImageReader.OnImageAvailableListener() {
#Override
public void onImageAvailable(ImageReader reader) {
// get the newest frame
Image image = reader.acquireNextImage();
if (image == null) {
return;
}
Image.Plane Y_plane = image.getPlanes()[0];
int Y_rowStride = Y_plane.getRowStride();
Image.Plane U_plane = image.getPlanes()[1];
int U_rowStride = U_plane.getRowStride();
Image.Plane V_plane = image.getPlanes()[2];
int V_rowStride = V_plane.getRowStride();
JNIUtils.RGBADisplay(image.getWidth(), image.getHeight(), Y_rowStride, Y_plane.getBuffer(), U_rowStride, U_plane.getBuffer(), V_rowStride, V_plane.getBuffer(), surface);
image.close();
}
};
JNI:
public static native void RGBADisplay(int srcWidth, int srcHeight, int Y_rowStride, ByteBuffer Y_Buffer, int U_rowStride, ByteBuffer U_Buffer, int V_rowStride, ByteBuffer V_Buffer, Surface surface);
C++:
const uint8_t NUM_128 = 128;
const uint8_t NUM_255 = 255;
JNIEXPORT void JNICALL Java_tau_camera2demo_JNIUtils_RGBADisplay(
JNIEnv *env,
jobject obj,
jint srcWidth,
jint srcHeight,
jint Y_rowStride,
jobject Y_Buffer,
jint U_rowStride,
jobject U_Buffer,
jint V_rowStride,
jobject V_Buffer,
jobject surface) {
uint8_t *srcYPtr = reinterpret_cast<uint8_t *>(env->GetDirectBufferAddress(Y_Buffer));
uint8_t *srcUPtr = reinterpret_cast<uint8_t *>(env->GetDirectBufferAddress(U_Buffer));
uint8_t *srcVPtr = reinterpret_cast<uint8_t *>(env->GetDirectBufferAddress(V_Buffer));
ANativeWindow * window = ANativeWindow_fromSurface(env, surface);
ANativeWindow_acquire(window);
ANativeWindow_Buffer buffer;
//set output size and format
//only 3 formats are available:
//WINDOW_FORMAT_RGBA_8888(DEFAULT), WINDOW_FORMAT_RGBX_8888, WINDOW_FORMAT_RGB_565
ANativeWindow_setBuffersGeometry(window, 0, 0, WINDOW_FORMAT_RGBA_8888);
if (int32_t err = ANativeWindow_lock(window, &buffer, NULL)) {
LOGE("ANativeWindow_lock failed with error code: %d\n", err);
ANativeWindow_release(window);
}
//convert YUV_420_888 to RGBA_8888 and display
uint8_t * outPtr = reinterpret_cast<uint8_t *>(buffer.bits);
for (size_t y = 0; y < srcHeight; y++)
{
uint8_t * Y_rowPtr = srcYPtr + y * Y_rowStride;
uint8_t * U_rowPtr = srcUPtr + (y >> 1) * U_rowStride;
uint8_t * V_rowPtr = srcVPtr + (y >> 1) * V_rowStride;
for (size_t x = 0; x < srcWidth; x++)
{
//from Wikipedia article YUV:
//Integer operation of ITU-R standard for YCbCr(8 bits per channel) to RGB888
//Y-Y, U-Cb, V-Cr
//R = Y + V + (V >> 2) + (V >> 3) + (V >> 5);
//G = Y - ((U >> 2) + (U >> 4) + (U >> 5)) - ((V >> 1) + (V >> 3) + (V >> 4) + (V >> 5));
//B = Y + U + (U >> 1) + (U >> 2) + (U >> 6);
uint8_t Y = Y_rowPtr[x];
uint8_t U = U_rowPtr[(x >> 1)] - NUM_128;
uint8_t V = V_rowPtr[(x >> 1)] - NUM_128;
*(outPtr++) = Y + V + (V >> 2) + (V >> 3) + (V >> 5); //R
*(outPtr++) = Y - ((U >> 2) + (U >> 4) + (U >> 5)) - ((V >> 1) + (V >> 3) + (V >> 4) + (V >> 5)); //G
*(outPtr++) = Y + U + (U >> 1) + (U >> 2) + (U >> 6); //B
*(outPtr++) = NUM_255; // gamma for RGBA_8888
}
}
ANativeWindow_unlockAndPost(window);
ANativeWindow_release(window);
}
The whole demo could be found here on Github: https://github.com/Fung-yuantao/android-camera2demo
UPDATE:
Added the following code after the line calling JNIUtils.RGBADisplay:
Log.d(TAG, "Y plane pixel stride: " + Y_plane.getPixelStride());
Log.d(TAG, "U plane pixel stride: " + U_plane.getPixelStride());
Log.d(TAG, "V plane pixel stride: " + V_plane.getPixelStride());
In Logcat:
09-07 06:40:02.576 5376-5392/tau.camera2demo D/Camera2Demo: Y plane pixel stride: 1
09-07 06:40:02.576 5376-5392/tau.camera2demo D/Camera2Demo: U plane pixel stride: 1
09-07 06:40:02.576 5376-5392/tau.camera2demo D/Camera2Demo: V plane pixel stride: 1
The image format should be planar according to the answer from alijandro.
The image output format for YUV_420_888 might be planar(I420, YV12) or semiplanar(NV12, NV21) format, from the documentation here.
So how to know it's planar or semi-planar format?
I guess you can find by image.getPlanes()[1].getPixelStride(). If it's 2, the image format is semi-planar format and has the following bit pattern.
YYYYYYYY UVUVUVUV ...
In my test environment, the output image format from ImageReader is semi-planar.
For semi-planar, we only need to handle the first two planar.
Change your code like following.
ANativeWindow_setBuffersGeometry(window, srcWidth, srcHeight, WINDOW_FORMAT_RGBA_8888);
if (int32_t err = ANativeWindow_lock(window, &buffer, NULL)) {
LOGE("ANativeWindow_lock failed with error code: %d\n", err);
ANativeWindow_release(window);
}
//convert YUV_420_888 to RGBA_888 and display
uint8_t * outPtr = reinterpret_cast<uint8_t *>(buffer.bits);
for (size_t y = 0; y < srcHeight; y++)
{
uint8_t * Y_rowPtr = srcYPtr + y * Y_rowStride;
uint8_t * UV_rowPtr = srcUPtr + (y >> 1) * Y_rowStride;
// uint8_t * V_rowPtr = srcVPtr + (y >> 1) * Y_rowStride / 4;
for (size_t x = 0; x < srcWidth; x++)
{
uint8_t Y = Y_rowPtr[x];
size_t uIndex = x & 0xfffffffe;
uint8_t U = UV_rowPtr[uIndex];
uint8_t V = UV_rowPtr[uIndex + 1];
double R = ((Y-16) * 1.164 + (V-128) * 1.596);
double G = ((Y-16) * 1.164 - (U-128) * 0.392 - (V-128) * 0.813);
double B = ((Y-16) * 1.164 + (U-128) * 2.017);
*(outPtr++) = (uint8_t) (R > 255 ? 255 : (R < 0 ? 0 : R));
*(outPtr++) = (uint8_t) (G > 255 ? 255 : (G < 0 ? 0 : G));
*(outPtr++) = (uint8_t) (B > 255 ? 255 : (B < 0 ? 0 : B));
*(outPtr++) = NUM_255; // gamma for RGBA_8888
}
}
For your planar output format, try use the YUV2RGB transform method below.
for (size_t y = 0; y < srcHeight; y++)
{
uint8_t * Y_rowPtr = srcYPtr + y * Y_rowStride;
uint8_t * U_rowPtr = srcUPtr + (y >> 1) * Y_rowStride / 2;
uint8_t * V_rowPtr = srcVPtr + (y >> 1) * Y_rowStride / 2;
for (size_t x = 0; x < srcWidth; x++)
{
uint8_t Y = Y_rowPtr[x];
uint8_t U = U_rowPtr[(x >> 1)];
uint8_t V = V_rowPtr[(x >> 1)];
double R = ((Y-16) * 1.164 + (V-128) * 1.596);
double G = ((Y-16) * 1.164 - (U-128) * 0.392 - (V-128) * 0.813);
double B = ((Y-16) * 1.164 + (U-128) * 2.017);
*(outPtr++) = (uint8_t) (R > 255 ? 255 : (R < 0 ? 0 : R));
*(outPtr++) = (uint8_t) (G > 255 ? 255 : (G < 0 ? 0 : G));
*(outPtr++) = (uint8_t) (B > 255 ? 255 : (B < 0 ? 0 : B));
*(outPtr++) = NUM_255; // gamma for RGBA_8888
}
}
I want a 16x16 monochromatic sprite to be displayed on my screen. The sprite has 8 different shades, which correspond to 8 different colours. Each color can have his own shade as well, but I want to limit that to 8. So instead of FF00FF, I want it to be like 707.
I've got the project on github
What I've got so far is as follows:
This is where I create all the possible colors:
int[] colours = new int[8 * 8 * 8];
int index = 0;
for(int r = 0; r < 8; r++) {
for(int g = 0; g < 8; g++) {
for(int b = 0; b < 8; b++) {
int rr = r * 255 / 7;
int gg = g * 255 / 7;
int bb = b * 255 / 7;
colours[index++] = rr << 16 | gg << 8 | bb;
}
}
}
This is where I want to call a long int containing all the color information:
public class Colours {
public static int get(int c1, int c2, int c3, int c4, int c5, int c6, int c7, int c8) {
//bit shifting with 56 on an integer removes data
return((get(c8) << 28) + (get(c7) << 24) + (get(c6) << 20) + (get(c5) >> 16) +
(get(c4) << 12) + (get(c3) << 8) + (get(c2) << 4) + (get(c1)));
}
private static int get(int colour) {
if(colour < 0) {
return 255;
}
int r = colour / 100 % 10;
int g = colour / 10 % 10;
int b = colour % 10;
return r * 64 + g * 8 + b;
}
}
This is where the screen gets rendered:
public void render(int xPos, int yPos, int tile, int colour) {
xPos -= xOffset;
yPos -= yOffset;
int xTile = tile % 32;
int yTile = tile / 32;
int tileOffset = (xTile << 4) + (yTile << 4) * sheet.width;
for (int y = 0; y < 16; y++) {
if (y + yPos < 0 || y + yPos >= height) {
continue;
}
int ySheet = y;
for (int x = 0; x < 16; x++) {
if (x + xPos < 0 || x + xPos >= width) {
continue;
}
int xSheet = x;
int col = (colour >> (sheet.pixels[xSheet + ySheet * sheet.width + tileOffset] * 16)) & 255;
if (col < 255) {
pixels[(x + xPos) + (y + yPos) * width] = col;
}
}
}
}
I fixed it by returning an integer array.
I am trying to convert JPEG byte[] data from Camera.PictureCallback to NV21 byte[] format but it didn't work.
I try to do this:
byte [] getNV21(int inputWidth, int inputHeight, Bitmap scaled) {
int [] argb = new int[inputWidth * inputHeight];
scaled.getPixels(argb, 0, inputWidth, 0, 0, inputWidth, inputHeight);
byte [] yuv = new byte[inputWidth*inputHeight*3/2];
encodeYUV420SP(yuv, argb, inputWidth, inputHeight);
scaled.recycle();
return yuv;
}
void encodeYUV420SP(byte[] yuv420sp, int[] argb, int width, int height) {
final int frameSize = width * height;
int yIndex = 0;
int uvIndex = frameSize;
int a, R, G, B, Y, U, V;
int index = 0;
for (int j = 0; j < height; j++) {
for (int i = 0; i < width; i++) {
a = (argb[index] & 0xff000000) >> 24; // a is not used obviously
R = (argb[index] & 0xff0000) >> 16;
G = (argb[index] & 0xff00) >> 8;
B = (argb[index] & 0xff) >> 0;
// well known RGB to YUV algorithm
Y = ( ( 66 * R + 129 * G + 25 * B + 128) >> 8) + 16;
U = ( ( -38 * R - 74 * G + 112 * B + 128) >> 8) + 128;
V = ( ( 112 * R - 94 * G - 18 * B + 128) >> 8) + 128;
// NV21 has a plane of Y and interleaved planes of VU each sampled by a factor of 2
// meaning for every 4 Y pixels there are 1 V and 1 U. Note the sampling is every other
// pixel AND every other scanline.
yuv420sp[yIndex++] = (byte) ((Y < 0) ? 0 : ((Y > 255) ? 255 : Y));
if (j % 2 == 0 && index % 2 == 0) {
yuv420sp[uvIndex++] = (byte)((V<0) ? 0 : ((V > 255) ? 255 : V));
yuv420sp[uvIndex++] = (byte)((U<0) ? 0 : ((U > 255) ? 255 : U));
}
index ++;
}
}
}
I tried to convert this Java code:
// http://www.stanford.edu/class/ee368/Android/index.html
// Source: http://www.stanford.edu/class/ee368/Android/HelloViewfinder/Project.zip
private void decodeYUV420RGB(int[] rgb, byte[] yuv420sp, int width, int height) {
Convert YUV to RGB
final int frameSize = width * height;
for (int j = 0, yp = 0; j < height; j++) {
int uvp = frameSize + (j >> 1) * width, u = 0, v = 0;
for (int i = 0; i < width; i++, yp++) {
int y = (0xff & ((int) yuv420sp[yp])) - 16;
if (y < 0) y = 0;
if ((i & 1) == 0) {
v = (0xff & yuv420sp[uvp++]) - 128;
u = (0xff & yuv420sp[uvp++]) - 128;
}
int y1192 = 1192 * y;
int r = (y1192 + 1634 * v);
int g = (y1192 - 833 * v - 400 * u);
int b = (y1192 + 2066 * u);
if (r < 0) r = 0; else if (r > 262143) r = 262143;
if (g < 0) g = 0; else if (g > 262143) g = 262143;
if (b < 0) b = 0; else if (b > 262143) b = 262143;
rgb[yp] = 0xff000000 | ((r << 6) & 0xff0000) | ((g >> 2) & 0xff00) | ((b >> 10) & 0xff);
}
}
}
which is called this way:
//byte[] mYUVData; int[] mRGBData;
decodeYUV420RGB(mRGBData, mYUVData, mImageWidth, mImageHeight);
to this C code:
#include <string.h>
#include <jni.h>
jint*
Java_com_camera_DrawOnTop_decodeYUV420RGB565(JNIEnv* env, jobject thiz, jintArray rgb, jbyteArray yuv420sp, jint width, jint height)
{
jbyte* yuv420spc = (*env)->GetByteArrayElements(env, yuv420sp, NULL);
jint* rgbc = (*env)->GetIntArrayElements(env, rgb, NULL);
int frameSize = width * height;
int j;
int i;
int yp;
for (j = 0, yp = 0; j < height; j++) {
int uvp = frameSize + (j >> 1) * width, u = 0, v = 0;
for (i = 0; i < width; i++, yp++) {
int y = (0xff & ((int) yuv420spc[yp])) - 16;
if (y < 0) y = 0;
if ((i & 1) == 0) {
v = (0xff & yuv420spc[uvp++]) - 128;
u = (0xff & yuv420spc[uvp++]) - 128;
}
int y1192 = 1192 * y;
int r = (y1192 + 1634 * v);
int g = (y1192 - 833 * v - 400 * u);
int b = (y1192 + 2066 * u);
if (r < 0) r = 0; else if (r > 262143) r = 262143;
if (g < 0) g = 0; else if (g > 262143) g = 262143;
if (b < 0) b = 0; else if (b > 262143) b = 262143;
rgbc[yp] = 0xff000000 | ((r << 6) & 0xff0000) | ((g >> 2) & 0xff00) | ((b >> 10) & 0xff);
}
}
(*env)->ReleaseByteArrayElements(env, yuv420sp, yuv420spc, 0 );
(*env)->ReleaseIntArrayElements(env, rgb, rgbc, 0 );
return rgbc;
}
and call it via JNI:
//int[] mRGBData; int [] tmpData = {1,2,3};
mRGBData = decodeYUV420RGB565(tmpData, mYUVData, mImageWidth, mImageHeight);
But the program breaks running after the above call.
I don't now how to do call by reference with JNI so I used tmpData
only to have data but return real data to mRGBData via equals sign.
What's wrong with my C code so it breaks at running time?
And what have I to change so that it works with reference like
the original code (without equals sign)?
You should provide to your JNI function an RGB array of correct size (that is, w*h), and an YUV array of correct size and structure (w*h*3/2, with w*h luma bytes (Y), followed by (w/2)*(h/2) pairs of chroma (U and V) bytes. The call will crash if you provide rgb array of size 3, as in your snippet.
Also note that you are building an rgb565 array. Its elements are probably expected to be of type short (16 bit) and not int (32 bit).
GetByteArrayElements() creates an array on the C side, which you fill, then release, then return to Java, which gets a pointer to released memory. If you want to actually get the data over to the Java side, you need to create a new Java array object and return that. Or, create the new object on the Java side and pass it to the C code to modify. I find the latter is usually easier. I usually create a DirectByteBuffer on the Java side and pass it into the native function, let the native function call GetDirectBufferAddress and write into that.
For an example, see my PD ojrandlib JNI code: https://github.com/lcrocker/ojrandlib/tree/master/source/java/com/onejoker/randlib
I changed code to this:
Java:
native void decodeYUV420RGB565(ByteBuffer rgb, byte[] yuv420sp, int width, int height);
//There's no allocateDirect for IntBuffer so I have to use ByteBuffer
//although mRGBData is an int array
//allocateDirect(mRGBData.length * 4) because mRGBData is an int array
ByteBuffer mTempData = ByteBuffer.allocateDirect(mRGBData.length*4);
decodeYUV420RGB565(mTempData, mYUVData, mImageWidth, mImageHeight);
mRGBData = mTempData.asIntBuffer().array();
mTempData.clear();
C:
void Java_com_camera_DrawOnTop_decodeYUV420RGB565(JNIEnv* env, jobject thiz, jintArray rgb, jbyteArray yuv420sp, jint width, jint height)
{
jbyte* yuv420spc = (*env)->GetByteArrayElements(env, yuv420sp, NULL);
jint* rgbc = (*env)->GetDirectBufferAddress(env, thiz);
//...conversion code as above...
(*env)->ReleaseByteArrayElements(env, yuv420sp, yuv420spc, 0 );
(*env)->ReleaseIntArrayElements(env, rgb, rgbc, 0 );
But I have the same problem: Code crashes when I call decodeYUV420RGB565(mTempData, mYUVData, mImageWidth, mImageHeight). What have I to change in the code?