I'm working on an app that needs to apply perspective distortion correction to a photo taken with the phone's camera.
Once the photo is taken, the idea is to show it on an imageview and let the user mark the four corners of the document (a card, a sheet of paper, etc.) and then apply the correction based on those points.
This is an example of what im trying to achieve:
http://1.bp.blogspot.com/-ro9hniPj52E/TkoM0kTlEnI/AAAAAAAAAbQ/c2R5VrgmC_w/s640/s4.jpg
Any ideas on how to do this on android?
You don't have to use a library for this.
You can just as well use one of the drawBitmap functions of the Canvas class with a matrix that's initialized using the setPolyToPoly function of the Matrix class.
public static Bitmap cornerPin(Bitmap b, float[] srcPoints, float[] dstPoints) {
int w = b.getWidth(), h = b.getHeight();
Bitmap result = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);
Canvas c = new Canvas(result);
Matrix m = new Matrix();
m.setPolyToPoly(srcPoints, 0, dstPoints, 0, 4);
c.drawBitmap(b, m, p);
return result;
}
(The Paint object is only needed to enable anti-aliasing.)
Usage:
int w = bitmap.getWidth(), h = bitmap.getHeight();
float[] src = {
0, 0, // Coordinate of top left point
0, h, // Coordinate of bottom left point
w, h, // Coordinate of bottom right point
w, 0 // Coordinate of top right point
};
float[] dst = {
0, 0, // Desired coordinate of top left point
0, h, // Desired coordinate of bottom left point
w, 0.8f * h, // Desired coordinate of bottom right point
w, 0.2f * h // Desired coordinate of top right point
};
Bitmap transformed = cornerPin(bitmap, src, dst);
Where src are the coordinates of the source points, dst are the coordinates of the destination points. Result:
What you want to do goes under various names of art, "corner-pin" being the one commonly used in the visual effects industry. You need to proceed in two steps:
Compute the mapping from the the desired, rectified image, to the original, distorted, image
Actually warp the original image according to the mapping computed in (1).
The 4 (non-collinear, perspective-distorted) corners of the original image, and the 4 corners of the target (undistorted) image, define the mapping. This mapping is called a "homography" - read the pointed wikipedia page for details. Once the mapping is known, the warping at step (2) can be computed by interpolation: for every pixel in the target image, find the corresponding pixel in the original image. As this will typically not be at integer coordinates, you interpolate its color from the neighbors. Various interpolation schemes are used, the common ones being nearest-neighbor, bilinear and bicubic (in increasing order of smoothness in the results).
For Android, I'd recommend installing the OpenCV SDK , and then use the geometry transformation routines (getPerspectiveTransform and warpPerspective for the two steps above).
Related
I am trying to further process a Camera2 image. Because the cameras in devices have different rotations and flipped based on back and front camera, I use transforms to properly rotate it.
transformationMatrix is that matrix for the front camera that has 270 rotation.
Then from that transformed camera image, I want to copy a scrolling window to another bitmap. I want to retain that bitmap/state and draw a line before drawing finalBitmapWithScanner on the phone screen.
Is there a way to do this more efficiently and fast? The second line takes 200ms to complete which is the main issue here.
Canvas canvas = new Canvas(tempBitmap);
canvas.drawBitmap(cameraBitmap, transformationMatrix, paint); // <= 200ms
Rect src = new Rect((int) lastXPos, 0, (int) mXPos, mViewHeight);
Canvas canvas2 = new Canvas(finalBitmap);
canvas2.drawBitmap(tempBitmap, src, src, paint);
Canvas canvas3 = new Canvas(finalBitmapWithScanner);
canvas3.drawBitmap(finalBitmap, 0, 0, paint);
canvas3.drawLine(mXPos, 0, mXPos, mViewHeight/2, scrollerPaint);
transformationMatrix.reset();
transformationMatrix.setRotate(270, imageHeight, 0);
transformationMatrix.postTranslate(-imageHeight, 0);
transformationMatrix.postScale(scaleFactor, scaleFactor);
transformationMatrix.postScale(-1f, 1f, mViewWidth / 2f, mViewHeight / 2f);
There are bunch of ways you can try to achieve fast rendering:
You can pass parameters "paint" an null.
also you can use function CreateScaledBitmap and notice you have to set scale and size before rendering as see in below:
As you can see in documentation enter link description here; you have to resize and rescale your bitmap before rendering so you can use code below for your BitmapFactory.Options :
mBitmapOptions.inScaled = true;
mBitmapOptions.inDensity = srcWidth;
mBitmapOptions.inTargetDensity = dstWidth;
// will load & resize the image to be 1/inSampleSize dimensions
mCurrentBitmap = BitmapFactory.decodeResources(getResources(),
mImageIDs, mBitmapOptions);
use canvas.restore() after draw func.
I have a View which is rotated around the X and Y axis using View.setRotationX and View.setRotationY. I have used View.getMatrix() and modified the values of the Matrix. I would now like to apply the Matrix back to the View but I have not found a good way of doing this without using the legacy Animation API in Android.
Basically what i need is to convert the Matrix values to View transformation values.
Example:
float[] src = new float[]{0, 0, view.getWidth(), 0, 0, view.getHeight(), view.getWidth(), view.getHeight()};
float[] dst = new float[8];
Matrix matrix = view.getMatrix();
matrix.mapPoints(dst, src);
dst[7] = NEW_Y_COORD_OF_CORNER;
matrix.setPolyToPoly(src, 0, dst, 0, dst.length >> 1);
//The matrix is now changed but the View is not.
So i would like to get rotation and translation of the Matrix to apply it back to the View:
float newRotationX = convertMatrixRotationToViewRotationX(matrix); //method i need
float newRotationY = convertMatrixRotationToViewRotationY(matrix); //method i need
float newTranslationX = convertMatrixRotationToViewTranslationX(matrix); //method i need
float newTranslationY = convertMatrixRotationToViewTranslationY(matrix); //method i need
view.setRotationY(newRotationX);
view.setRotationX(newRotationY);
view.setX(newTranslationX);
view.setY(newTranslationY);
This might seem like a far to complex way of transforming the View but I need to do it this way in order to be able to set x,y corner coordinates of the View. For more info of getting the corner coordinates of a way see my previous post here: https://stackoverflow.com/questions/24330073/how-to-get-all-4-corner-coordinates-of-a-view-in-android?noredirect=1#comment37672700_24330073
I am creating a button class that will display a blue circle if it is not being pressed and a red circle if it is being pressed. I start by building a 4 vertex quad composed of two triangles. I generate my own bitmap and draw two circles on the bitmap next to each other, left to right. I then create a texture buffer with instead 4 uv points, I create it with 8, one that maps out the blue circle, one that maps out the red circle. I would then like to render the red circle when the button is pressed. Ideally I would like to call the
gl.glTexCoordPointer
method and pass in an offset, but that is not working. Here is my method that I used to generate the bitmap, draw on the bitmap using the canvas and paint objects and then attempt to map the textures. Note that I have to generate a texture that is a power of 2 so there is some math in there that allows me to generate a bigger bitmap than I need based of the width and height variables of the button that were specified in the constructor.
public void InitializeButton(GL10 gl, int upcolor, int downcolor, String symbol)
{
//Our variables for creating a bitmap and texture
Canvas canvas = null;
Bitmap bitmap = null;
Paint paint = null;
//Set up the bitmap type
Bitmap.Config conf = Bitmap.Config.ARGB_8888;
/*
* We now want to calculate the size of the texture. Remember it is best to be a square
* texture or at least a power of 2. The below equation below will do this. For example
* if the width of the button was say 20, we need to find the smallest power of 2 that is
* greater than 20. In this case we know it is 32. But how do we calculate that? First we
* have to find out the exponent of what 2^x = 20. Then we find the ceiling of tha number.
* In order to make that calculation we have to take the log of that, but in order to use
* the log function which is base 10, we have to switch to base 2 so that means
* we have to take the log(width)/log(2) to switch to base 2, then get the ceiling of that
* number because it would be between 4 and 5 in this case. When we take the ceiling we get
* 5 and 2^5 is 32.
*
* Side note, we want to double the size to make sure there is room for the up and the down
* actions.
*/
widthTexture = (int) Math.pow(2,Math.ceil((Math.log(this.width*2)/Math.log(2))));
heightTexture = (int) Math.pow(2,Math.ceil((Math.log(this.height*2)/Math.log(2))));
/*
* Now we will create the bitmap for the creation of the button
*/
bitmap = Bitmap.createBitmap(widthTexture,heightTexture,conf);
//Now create a new canvas from that bitmap
canvas = new Canvas(bitmap);
//Create a new Paint
paint = new Paint();
/*
* Now we want to render the draw the up and down button on the texture. We are just going
* to use two different colors to represent up and down. So we will draw the up circle button
* starting at 0 0 and the down button off to the right.
*/
paint.setColor(upcolor);
paint.setAlpha(120);
canvas.drawOval(new RectF(0,0,width,height), paint);
paint.setColor(Color.BLACK);
canvas.drawText(symbol, width/2, height/2, paint);
paint.setColor(Color.WHITE);
canvas.drawText(symbol, width/2+3, height/2+3, paint);
//Draw the down color button
paint.setColor(downcolor);
paint.setAlpha(120);
canvas.drawOval(new RectF(width,0,width*2,height), paint);
paint.setColor(Color.WHITE);
canvas.drawText(symbol, width+(width/2), height/2, paint);
paint.setColor(Color.BLACK);
canvas.drawText(symbol, width+(width/2)+3, height/2+3, paint);
float widthpercent = ((float)width/(float)widthTexture);
float heightpercent = ((float)height/(float)heightTexture);
/*
* Now create two texture maps. One for the up button and one for the down button
* You can change the offset of the draw texture thing to change the animations now
*/
float uvTextures[] = {0f, heightpercent,
widthpercent, heightpercent,
widthpercent, 0f,
0f, 0f,
widthpercent, heightpercent,
widthpercent*2, heightpercent,
widthpercent*2, 0f,
widthpercent, 0f,
};
/*
* Allocate the byte buffer so it is a normal array of floats and not a java array.
* load the uvTexture values inside.
*/
ByteBuffer tbb = ByteBuffer.allocateDirect(uvTextures.length*4);
tbb.order(ByteOrder.nativeOrder());
textureBuffer = tbb.asFloatBuffer();
textureBuffer.put(uvTextures);
textureBuffer.position(0);
int [] textures = new int[1];
gl.glGenTextures(1, textures,0);
gl.glBindTexture(GL10.GL_TEXTURE_2D, textures[0]);
textureID = textures[0];
gl.glTexParameterf(GL10.GL_TEXTURE_2D,GL10.GL_TEXTURE_MIN_FILTER,GL10.GL_LINEAR);
gl.glTexParameterf(GL10.GL_TEXTURE_2D,GL10.GL_TEXTURE_MAG_FILTER,GL10.GL_LINEAR);
gl.glTexParameterf(GL10.GL_TEXTURE_2D,GL10.GL_TEXTURE_WRAP_S,GL10.GL_REPEAT);
gl.glTexParameterf(GL10.GL_TEXTURE_2D,GL10.GL_TEXTURE_WRAP_T,GL10.GL_REPEAT);
GLUtils.texImage2D(GL10.GL_TEXTURE_2D,0,bitmap,0);
//Don't forget to deallocate the bitmap
bitmap.recycle();
}
So eventually in the render method, I want to be able to render different coordinates on the texture map to the same vertices. So I call glTexCoordPointer and change the offset to "counter" where counter should have been 6*4 (6 vertices times 4 bytes per float) but that doesn't work, so I tried counter at 0 and incremented it and never found the magical number that will map the red button. On a side note, when I tried that, I would get very weird patterns drawn, sometimes showing 10 to 15 mini blue and red circles.
if(isdown)
gl.glTexCoordPointer(2,GL10.GL_FLOAT,counter,textureBuffer);
else
gl.glTexCoordPointer(2,GL10.GL_FLOAT,0,textureBuffer);
#Harism was correct for my situation. I hope I am still using this correctly. I was thinking the stride variable could be changed, but I had to change the position of the buffer. This worked for animating textures on 3D surfaces. I don't know if this is the "best" way to do it, but until then I'll be using this.
The mentioned class has got the following method:
protected void drawCompass(Canvas canvas, float bearing) {
int offset = Math.max(canvas.getHeight(), canvas.getWidth()) / 8;
Rect r = new Rect(0, 0, 2*offset, 2*offset);
canvas.drawBitmap(compassBase, null, r, paint);
canvas.rotate(-bearing, offset, offset);
canvas.drawBitmap(compassArrow, null, r, paint);
}
Link to the complete src: MyLocationOverlay
There they create a Rect r witch specifies where the both Bitmaps should be drawn on the canvas.
The java doc of drawBitmap says :"Draw the specified bitmap, scaling/translating automatically to fill the destination rectangle. If the source rectangle is not null, it specifies the subset of the bitmap to draw. "
Because both Bitmaps use the same Rect r and because they both are automatically scaled to fit the Rect, why is the result a perfect Compass when I activate the compass in the MapView.
To my mind the result should be crap, because the arrow of the compass is also scaled to fit the Rect.
So where is the error in reasoning?
They are scaled using the same proportion, not to absolute size. To answer your question, the canvas is drawn on using the bitmap as reference - think of it like a painter and a canvas painting one image on top of the other using two photo images as reference.
I have this sprite rotating algorithm (its poorly named and just used for testing). It is so close, sprites drawn with it do rotate. Everyframe I can add +5 degrees to it and see my nice little sprite rotate around. The problem is, the other stuff drawn to the canvas now flickers. If I don't do the rotation the regular drawn sprites work great. I think I am close but I just don't know what piece I am missing. Below is my two "Draw_Sprite" methods, one just draws the previously resource loaded bitmap to the canvas passed in. The other one, does some rotation the best I know how to rotate the sprite by so x many degrees..and then draw it. If I have a nice game loop that draws several objects, one type is the rotated kind. Then the non-rotated sprites flicker and yet the rotated sprite never does. Though if I draw the non-rotated sprites first, all is well, but then the Z-Ordering could be messed up (sprites on top of UI elements etc)... The method definitions:
/*************************************************
* rotated sprite, ignore the whatever, its for ease of use and testing to have this argument list
* #param c canvas to draw on.
* #param whatever ignore
* #param rot degrees to rotate
* #return
*/
public int Draw_Sprite(Canvas c, int whatever, int rot) {
//rotating sprite
Rect src = new Rect(0, 0, width, height);
Rect dst = new Rect(x, y, x + width, y + height);
Matrix orig = c.getMatrix();
mMatrix = orig;
orig.setTranslate(0, 0);
orig.postRotate(rot, x+width/2, y+height/2);
c.setMatrix(orig);
c.drawBitmap(images[curr_frame], src, dst, null);
c.setMatrix(mMatrix); //set it back so all things afterwards are displayed correctly.
isScaled=false;
return 1;
}
/********************************************************
* draw a regular sprite to canvas c
* #param c
* #return
*/
public int Draw_Sprite(Canvas c) {
Rect src = new Rect(0, 0, width, height);
Rect dst = new Rect(x, y, x + width, y + height);
c.drawBitmap(images[curr_frame], src, dst, null);
isScaled=false;
return 1;
}
And now the usage:
void onDraw(Canvas c)
{
canvas.drawRect( bgRect, bgPaint); //draw the background
//draw all game objects
// draw the normal items
for (GameEntity graphic : _graphics) {
graphic.toScreenCoords((int)player_x, (int)player_y);
if(graphic.getType().equals("planet")) //draw planets
graphic.Draw_Sprite(canvas); //before the rotation call draws fine
else
{
//rotate all space ships every frame so i see them spinning
//test rotation
mRot +=5;
if(mRot>=360)
mRot=0;
graphic.Draw_Sprite(canvas, 0, mRot); //yes function name will be better in future. this rotates spins draws fine
}
}
thePlayer.Draw_Sprite(canvas); //FLICKERS
drawUI(canvas);//all things here flickr
}
So it does do it, things after a call to a rotational draw are drawn correctly. But the problem is it flickrs. Now One could say I should just do all my non rotational stuff and save that last, but the zordering would be off.... suggestions as to how to tackle this issue of zordering or the flickering?
Just for the next guy who may read this. You can do this with only a few lines of code:
canvas.save();
canvas.rotate(rotation_angle, x + (widthofimage / 2), y + (heightofimage / 2));
canvas.drawBitmap(bitmap, x, y, null);
canvas.restore();
Try using canvas.save() before the rotation and canvas.restore() after manipulation is complete.
When performing manipulations on the canvas in order to change the way an object is drawn you have to remember the manipulations set how the canvas handles origins etc... So if you translate or rotate the canvas, that will be set for the lifetime of that canvas. In order to avoid this you first call save, which saves a snapshot of the canvas matrix before you manipulate it, then you run all your changes, then call restore which will restore the canvas back to the last saved point. Otherwise all your changes build up and you get unintended results.