So I am using the API to detect faces in images, and it is working well for me so far. I have not been able to figure out how to crop the image to the face however. I know how to crop the Bitmap, but it requires getting the top left position of the face in the Bitmap and width and height. When I query for the top left position using
points = face.getPosition();
Bitmap bmp = Bitmap.createBitmap(bit,(int)points.x,(int)(-1.0*points.y),(int)face.getWidth(),(int)face.getHeight());
But when I look at points, I notice that y is -63.5555 and x is 235.6666; I dont understand why there is a negative y coordinate. I did some Debugging and looked inside the face object; I found that it contained a PointF object already that had positive x and y coordinates. So why is a negative y coordinate being returned in this case?
The bounding box estimates the dimensions of the head, even though it may not be entirely visible within the photo. The coordinates may be negative if the face is cropped by the top or left of the image (e.g., the top of the head is cropped off the top of the picture, resulting in a y coordinate above 0).
The difference that you see in debugging is due to that fact that the implementation internally uses the head center position to represent the position (approximately at the mid-point between the eyes), but the API translates this to the top-left position when you call getPosition, for your convenience.
Also note that the bounding box is not necessarily a tight bounds on the face. If you want a tighter fit, you should enable landmark detection and compute your desired level of cropping relative to the returned landmarks.
I have used the same API before and was able to successfully crop the face.
Try
//Crop face option
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
//Bitmap bitmap = BitmapFactory.decodeFile(pictureFile.getAbsolutePath(), options);
Bitmap bitmap = getRotatedImageToUpload(pictureFile.getAbsolutePath());
Bitmap faceBitmap = Bitmap.createBitmap(bitmap, (int) faceCentre.x, (int) faceCentre.y, (int) faceWidth, (int) faceHeight);
FileOutputStream out = null;
try {
out = new FileOutputStream(getOutputMediaFile());
faceBitmap.compress(Bitmap.CompressFormat.PNG, 100, out); // bmp is your Bitmap instance
// PNG is a lossless format, the compression factor (100) is ignored
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (out != null) {
out.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
//End of Crop face option
And the code for getRotateImageToUpload is
public Bitmap getRotatedImageToUpload(String filePath) {
try {
String file = filePath;
BitmapFactory.Options bounds = new BitmapFactory.Options();
bounds.inJustDecodeBounds = true;
BitmapFactory.decodeFile(file, bounds);
BitmapFactory.Options opts = new BitmapFactory.Options();
Bitmap bm = BitmapFactory.decodeFile(file, opts);
ExifInterface exif = null;
exif = new ExifInterface(file);
String orientString = exif.getAttribute(ExifInterface.TAG_ORIENTATION);
int orientation = orientString != null ? Integer.parseInt(orientString) : ExifInterface.ORIENTATION_NORMAL;
int rotationAngle = 0;
if (orientation == ExifInterface.ORIENTATION_ROTATE_90) rotationAngle = 90;
if (orientation == ExifInterface.ORIENTATION_ROTATE_180) rotationAngle = 180;
if (orientation == ExifInterface.ORIENTATION_ROTATE_270) rotationAngle = 270;
Matrix matrix = new Matrix();
matrix.setRotate(rotationAngle, (float) bm.getWidth() / 2, (float) bm.getHeight() / 2);
Bitmap rotatedBitmap = Bitmap.createBitmap(bm, 0, 0, bounds.outWidth, bounds.outHeight, matrix, true);
return rotatedBitmap;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
Related
When I use following code, it ends up with outofmemory exception. After doing researh Render script looks like a good candidate. Where can I find sample code for similar operation and how can integrate it to my project.
public Bitmap rotateBitmap(Bitmap image, int angle) {
if (image != null) {
Matrix matrix = new Matrix();
matrix.postRotate(angle, (image.getWidth()) / 2,
(image.getHeight()) / 2);
return Bitmap.createBitmap(image, 0, 0, image.getWidth(),
image.getHeight(), matrix, true);
}
return null;
}
Basically rotating bitmap is a task of rotating 2D array without using additional memory. And this is the correct implementation with RenderScript: Android: rotate image without loading it to memory .
But this is not necessary if all you want is just to display rotated Bitmap. You can simply extend ImageView and rotate the Canvas while drawing on it:
canvas.save();
canvas.rotate(angle, X + (imageW / 2), Y + (imageH / 2));
canvas.drawBitmap(imageBmp, X, Y, null);
canvas.restore();
What about ScriptIntrinsic, since it's just a built-in RenderScript kernels for common operations you cannot do nothing above the already implemented functions: ScriptIntrinsic3DLUT, ScriptIntrinsicBLAS, ScriptIntrinsicBlend, ScriptIntrinsicBlur, ScriptIntrinsicColorMatrix, ScriptIntrinsicConvolve3x3, ScriptIntrinsicConvolve5x5, ScriptIntrinsicHistogram, ScriptIntrinsicLUT, ScriptIntrinsicResize, ScriptIntrinsicYuvToRGB. They do not include functionality to rotate bitmap at the moment so you should create your own ScriptC script.
Try this code..
private Bitmap RotateImage(Bitmap _bitmap, int angle) {
Matrix matrix = new Matrix();
matrix.postRotate(angle);
_bitmap = Bitmap.createBitmap(_bitmap, 0, 0, _bitmap.getWidth(), _bitmap.getHeight(), matrix, true);
return _bitmap;
}
Use this code when select image from gallery.
like this..
File _file = new File(file_name);
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 1;
Bitmap bitmap = BitmapFactory.decodeFile(file_name, options);
try {
ExifInterface exif = new ExifInterface(file_name);
int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 1);
if (orientation == ExifInterface.ORIENTATION_ROTATE_90) {
bitmap = RotateImage(bitmap, 90);
} else if (orientation ==ExifInterface.ORIENTATION_ROTATE_270) {
bitmap = RotateImage(bitmap, 270);
}
} catch (Exception e) {
e.printStackTrace();
}
image_holder.setImageBitmap(bitmap);
I want to implement my own face detection/recognition android app. When camera finds some face, a rectangle is displayed on camera preview (in real time). App has method for taking photos too. However, I dont want to save whole picture, only the area within the rectangle - the human face. When I give the rectangle coordinates to Bitmap.createBitmap method to crop my picture, correctness of cropped photo depends on the place on display, where the rectangle was shown. When a detected face appears in the middle of preview, createBitmap crops it circa fine, but not if it shows on left or right side of the display. Seems like the coordinates I send to Bitmap.createBitmap are conversed but I cannot find the ratio. Any solutions?
Here is my onPictureTaken method:
#Override
public void onPictureTaken(byte[] data, Camera camera) {
File pictureFile = getOutputMediaFile();
if (pictureFile == null) {
Log.d(TAG, "Error creating media file, check storage permissions: ");
return;
}
Bitmap picture = BitmapFactory.decodeByteArray(data, 0, data.length);
RectF faceRect = mPreview.getFaceRect();
float x = faceRect.left;
float y = faceRect.top;
float w = faceRect.right - faceRect.left;
float h = faceRect.bottom - faceRect.top;
int intX = (int) x;
int intY = (int) y;
int intW = (int) w;
int intH = (int) h;
Bitmap croppedPicture = Bitmap.createBitmap(picture, intX, intY, intW, intH);
ByteArrayOutputStream stream = new ByteArrayOutputStream();
croppedPicture.compress(Bitmap.CompressFormat.JPEG, 100, stream);
byte[] byteArrayFromPicture = stream.toByteArray();
try {
FileOutputStream fos = new FileOutputStream(pictureFile);
fos.write(byteArrayFromPicture);
//fos.write(data);
fos.close();
} catch (FileNotFoundException e) {
Log.d(TAG, "File not found: " + e.getMessage());
} catch (IOException e) {
Log.d(TAG, "Error accessing file: " + e.getMessage());
}
}
and here is some example of cropped picture, I do not have enough reputation to post more links:
face close to the left edge of display
cropped pic1
(sorry about making picture of picture, I was lazy to implement saving the rectangle together with photo)
Resolved
The solution was very simple - because of using frontal camera the captured image was always reflected, added two if-clauses:
Bitmap picture = BitmapFactory.decodeByteArray(data, 0, data.length);
RectF faceRect = mPreview.getFaceRect();
Camera.Parameters parameters = mCamera.getParameters();
int picWidth = parameters.getPictureSize().width;
int intX = 0;
int intY = (int) faceRect.top;
int intW = (int) (faceRect.right - faceRect.left);
int intH = (int) (faceRect.bottom - faceRect.top);
if(faceRect.left > picWidth / 2) {
intX = (int) (faceRect.right - (faceRect.right - picWidth / 2) * 2);
}
else if(faceRect.left <= picWidth / 2) {
intX = (int) (picWidth - faceRect.right);
}
Bitmap croppedPicture = Bitmap.createBitmap(picture, intX, intY, intW, intH);
ByteArrayOutputStream stream = new ByteArrayOutputStream();
croppedPicture.compress(Bitmap.CompressFormat.JPEG, 100, stream);
byte[] byteArrayFromPicture = stream.toByteArray();
In the app I'm working on, part of the user's input is a series of images. Some of these might be 4MB large in their raw form. I resize and rotate them, then save them in the app's portion of the device memory for later use. The problem I'm experiencing is that I seem to run out of memory even though I recycle each Bitmap after it's saved.
Here's the main processing
private class SaveImagesTask extends AsyncTask<Long, Void, Void>{
#Override
protected Void doInBackground(Long... ids){
long id = ids[0];
Iterator<ImageButton> itImg = arrBtnImage.iterator();
Iterator<TextView> itLbl = arrLblImage.iterator();
while(itImg.hasNext() && itLbl.hasNext()){
String imgPath = (String) itImg.next().getTag();
String imgLbl = itLbl.next().getText().toString().trim();
String imgName = imgLbl.replace(" ", "_").replace(",", "_");
imgName += ".jpg";
if(imgPath != null){
/* Save resized version of image */
File dir = getApplicationContext().getFilesDir();
dir = new File(dir, "temp/" + Long.toString(plantId));
boolean madeDir = dir.mkdirs();
File path = new File(dir, imgName);
Bitmap toSave = getScaledBitmap(imgPath, IMAGE_MAX_SIDE_LENGTH, IMAGE_MAX_SIDE_LENGTH);
try{
BufferedOutputStream outStream = new BufferedOutputStream(new FileOutputStream(path));
boolean insertSuccess = toSave.compress(Bitmap.CompressFormat.JPEG, 90, outStream);
outStream.close();
}
catch(FileNotFoundException e){
e.printStackTrace();
}
catch(IOException e){
e.printStackTrace();
}
toSave.recycle();
}//if
}//while(more images to process)
}// method: doInBackground(params)
}// inner class: saveImages extends AsyncTask
And here's where I resize the image
private Bitmap getScaledBitmap(String picturePath, int newWidth, int newHeight){
/* Size */
BitmapFactory.Options sizeOptions = new BitmapFactory.Options();
sizeOptions.inJustDecodeBounds = true;
BitmapFactory.decodeFile(picturePath, sizeOptions);
int sampleSize = 1;
int rawHeight = sizeOptions.outHeight;
int rawWidth = sizeOptions.outWidth;
if(rawHeight > newHeight || rawWidth > newWidth){
/* Find the dimension that needs to change the most */
int heightRatio = Math.round((float) rawHeight / (float) newHeight);
int widthRatio = Math.round((float) rawWidth / (float) newWidth);
sampleSize = (heightRatio > widthRatio ? heightRatio : widthRatio);
}//if(raw image is wider or taller than it should be){reduce size so neither is too large}
sizeOptions.inJustDecodeBounds = false;//Load pixels for display.
sizeOptions.inSampleSize = sampleSize;//Set shrink factor.
Bitmap scaledBitmap = BitmapFactory.decodeFile(picturePath, sizeOptions);
/* Rotation */
int rotation = 1;
try{
ExifInterface exif = new ExifInterface(picturePath);
rotation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
}
catch(IOException e){
e.printStackTrace();
}
int rotationInDegrees = 0;
if(rotation == ExifInterface.ORIENTATION_ROTATE_90)
rotationInDegrees = 90;
else if(rotation == ExifInterface.ORIENTATION_ROTATE_180)
rotationInDegrees = 180;
else if(rotation == ExifInterface.ORIENTATION_ROTATE_270)
rotationInDegrees = 270;
Matrix matrix = new Matrix();
if(rotation != 0f)
matrix.preRotate(rotationInDegrees);
return Bitmap.createBitmap(scaledBitmap, 0, 0,
scaledBitmap.getWidth(), scaledBitmap.getHeight(), matrix, true);
}// method: getScaledBitmap(String, int, int)
Before I start getting comments about this being so common of a question, I'll point out that I'm not displaying these images, so it's not like I'm trying to keep all of these in memory. I need to keep large images because users will want to be able to zoom in on the pictures, but I'm resizing them because they don't need to be ridiculously huge. Pretty much any other solution I've seen on SO for images and OOM errors don't apply to my back-to-back access of multiple images.
So like I said, I'm recycling each Bitmap after it's saved, but they still seem to be using memory. Any idea what I'm missing?
You're not recycling scaledBitmap in getScaledBitmap. Fixing that should help. Change this line:
return Bitmap.createBitmap(scaledBitmap, 0, 0,
scaledBitmap.getWidth(), scaledBitmap.getHeight(), matrix, true);
to something like:
Bitmap newBitmap = Bitmap.createBitmap(scaledBitmap, 0, 0,
scaledBitmap.getWidth(), scaledBitmap.getHeight(), matrix, true);
scaledBitmap.recycle();
return newBitmap;
If you have multiple threads working on large bitmaps, you will use a lot of memory on some cases.
What you need is to find the best approach according to your needs. here are some things you can do and/or need to know:
use a single thread for the images handling.
always recycle old bitmaps that you don't need anymore, as soon as possible. it's true that the GC will help you, but that can help it too, and it will work even on pre-honeycomb devices.
do the image manipulations via NDK (so you won't need to have 2 bitmaps for each image manipulation), for example using this.
downsample the image to the minimal size that you need, and never assume that the memory is large enough for any given image (unless you are 100% sure that the images are small).
remember that the requirements for android devices are still very low in terms of RAM per app (heap size) - the bare minimal is still 16MB per app.
you can use android:largeHeap="true" in the manifest, but that doesn't mean anything about how much more you will get, if at all.
My Problem
I have a series of Bitmaps that I would like to load up in the correct orientation.
When I save the image I go in and set the orientation attribute using the ExifInterface
ExifInterface exif = new ExifInterface(EXTERNAL_IMAGE_PATH+File.separator+this._currentPhotoName+JPEG_FILE_SUFFIX);
int rotation = CCDataUtils.exifToDegrees(exif.getAttributeInt(ExifInterface.TAG_ORIENTATION,ExifInterface.ORIENTATION_NORMAL));
Log.v("PhotoManager", "Rotation:"+rotation);
if (rotation > 0) {
exif.setAttribute(ExifInterface.TAG_ORIENTATION,String.valueOf(0));
This works fine and if I was to pull this image off of my device it would be in the correct orientation. However, when I then decode my Bitmap later down the line it stays in the camera's default orientation of left-horizontal even if the image was taken in portrait?
My Question
How can I decode the bitmap and take into account its EXIF information?
I don't want to have to rotate the image after I decode it every time as I would have to create another Bitmap and that is memory I don't have.
Thanks in advance.
For those that are also stuck on this and have oom issues when manipulating multiple bitmaps here is my solution.
Do not change the exif data like I originally thought in the question - We need this later down the line.
When it comes to decoding the image to view, instead of decoding the full size image just decode the image scaled down to what you need. The following code example contains both the decoding of the bitmap to the device screen size and then it also handles the rotation of the bitmap for you.
public static Bitmap decodeFileForDisplay(File f){
try {
//Decode image size
BitmapFactory.Options o = new BitmapFactory.Options();
o.inJustDecodeBounds = true;
BitmapFactory.decodeStream(new FileInputStream(f),null,o);
DisplayMetrics metrics = MyApplication.getAppContext().getResources().getDisplayMetrics();
//The new size we want to scale to
//final int REQUIRED_SIZE=180;
int scaleW = o.outWidth / metrics.widthPixels;
int scaleH = o.outHeight / metrics.heightPixels;
int scale = Math.max(scaleW,scaleH);
//Log.d("CCBitmapUtils", "Scale Factor:"+scale);
//Find the correct scale value. It should be the power of 2.
//Decode with inSampleSize
BitmapFactory.Options o2 = new BitmapFactory.Options();
o2.inSampleSize=scale;
Bitmap scaledPhoto = BitmapFactory.decodeStream(new FileInputStream(f), null, o2);
try {
ExifInterface exif = new ExifInterface(f.getAbsolutePath());
int rotation = CCDataUtils.exifToDegrees(exif.getAttributeInt(ExifInterface.TAG_ORIENTATION,ExifInterface.ORIENTATION_NORMAL));
if (rotation > 0)
scaledPhoto = CCBitmapUtils.convertBitmapToCorrectOrientation(scaledPhoto, rotation);
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
return scaledPhoto;
} catch (FileNotFoundException e) {}
return null;
}
public static Bitmap convertBitmapToCorrectOrientation(Bitmap photo,int rotation) {
int width = photo.getWidth();
int height = photo.getHeight();
Matrix matrix = new Matrix();
matrix.preRotate(rotation);
return Bitmap.createBitmap(photo, 0, 0, width, height, matrix, false);
}
So the image Bitmap thats returned after calling decodeFileForDisplay(File f); is in the correct orientation and the correct size for you screen saving you tons of memory problems.
I hope it helps someone
I'm trying to display a bitmap on canvas using the matrix.
canvas.drawBitmap(currentBitmap, m_matrix, tempPaint);
but the result appears, image seems weird. After that I have shown it using bounds
canvas.drawBitmap(currentBitmap, 0, 0, tempPaint);
But here image looks good, in both the cases image is not scaled.
How should I set matrix properties for the initial display?
Does the matrix display uses something else for showing the image because of that image is getting changed?
Please suggest any tutorials for details explanation.
After searching lot a lot i have finally found the solution, some of the threads related to same issue helped me out.
Quality Issue
Quality problems when resizing an image at runtime
I have followed the google's Tutorials for loading the bitmap and written my own function for scaling the bitmap.
public static Bitmap scaleDownBitmap(Bitmap original, boolean recycleOriginal, int newmaxWidth , int newmaxHeight){
if(original == null)
return null;
Bitmap rtr= null;
try{
int origWidth = original.getWidth();
int origHeight = original.getHeight();
if(origWidth <= newmaxWidth && origHeight <= newmaxWidth){
Bitmap b = Bitmap.createBitmap(original);
if (recycleOriginal && (original != b))
original.recycle();
return b;
}
int newWidth = 0;
int newHeight = 0;
float ratio;
if(origWidth > origHeight){
ratio = (float)origWidth/(float)origHeight;
newWidth = newmaxWidth;
newHeight = (int)((float)newWidth/ratio);
}
else{
ratio = (float)origHeight/(float)origWidth;
newHeight = newmaxHeight;
newWidth = (int)((float)newHeight/ratio);
}
rtr = CreateScaledBitmap(original , newWidth , newHeight);
if(recycleOriginal && original != rtr)
original.recycle();
}
catch (Exception e) {
Log.d("Image Compress Error", e.getMessage());
}
return rtr;
}
public static Bitmap CreateScaledBitmap(Bitmap paramBitmap, int paramInt1, int paramInt2)
{
Bitmap localBitmap = Bitmap.createBitmap(paramInt1, paramInt2, paramBitmap.getConfig());
Canvas localCanvas = new Canvas(localBitmap);
localCanvas.setDrawFilter(new PaintFlagsDrawFilter(0, 2));
localCanvas.drawBitmap(paramBitmap, new Rect(0, 0, paramBitmap.getWidth(), paramBitmap.getHeight()),
new Rect(0, 0, paramInt1, paramInt2), null);
return localBitmap;
}
Now after getting the image i have passed the following parameters to the paint.
tempPaint.setFilterBitmap(true); //Line 1
canvas.drawBitmap(currentBitmap, m_matrix, tempPaint);
You have to use Matrix . post scale to scale the image, like in the example below.
private void drawMatrix(){
Matrix matrix = new Matrix();
matrix.postScale(curScale, curScale);
canvas.drawBitmap(currentBitmap, matrix, tempPaint);}
Haven't tried it yet.... But should work...