Android Camera.Parameters setPictureSize not working - android

I am trying to set the best possible output picture size in my camera object. So that, i can get a perfect downscaled sample image and display it.
During debugging i observed i am setting output picture size exactly the size of my screen dimensions. But when i DecodeBounds of the returned image by camera. I get some larger number!
Also i am not setting my display dimensions as expected output picture size. Code used to calculate and set the output picture size is given below.
I am using this code for devices having API level < 21, so using camera shouldn't be a problem.
I don't have any idea of why i am getting this behavior. Thanks in advance for help!
Defining Camera parameter
Camera.Parameters parameters = mCamera.getParameters();
setOutputPictureSize(parameters.getSupportedPictureSizes(), parameters); //update paramters in this function.
//set the modified parameters back to mCamera
mCamera.setParameters(parameters);
Optimal picture size calculation
private void setOutputPictureSize(List<Camera.Size> availablePicSize, Camera.Parameters parameters)
{
if (availablePicSize != null) {
int bestScore = (1<<30); //set an impossible value.
Camera.Size bestPictureSize = null;
for (Camera.Size pictureSize : availablePicSize) {
int curScore = calcOutputScore(pictureSize); //calculate sore of the current picture size
if (curScore < bestScore) { //update best picture size
bestScore = curScore;
bestPictureSize = pictureSize;
}
}
if (bestPictureSize != null) {
parameters.setPictureSize(bestPictureSize.width, bestPictureSize.height);
}
}
}
//calculates score of a target picture size compared to screen dimensions.
//scores are non-negative where 0 is the best score.
private int calcOutputScore(Camera.Size pictureSize)
{
Point displaySize = AppData.getDiaplaySize();
int score = (1<<30);//set an impossible value.
if (pictureSize.height < displaySize.x || pictureSize.width < displaySize.y) {
return score; //return the worst possible score.
}
for (int i = 1; ; ++i) {
if (displaySize.x * i > pictureSize.height || displaySize.y * i > pictureSize.width) {
break;
}
score = Math.min(score, Math.max(pictureSize.height-displaySize.x*i, pictureSize.width-displaySize.y*i));
}
return score;
}

Finally i resolved the issue after many attempts! Below are my findings:
Step 1. If we are already previewing, call mCamera.stopPreview()
Step 2. Set modified parameters by calling mCamera.setParameters(...)
Step 3. Start previewing again, call mCamera.startPreview()
If i call mCamera.setParameters without stopping preview (Assuming camera is previewing). Camera seems to ignore the updated parameters.
I came up with this solution after several trail and errors. Anyone know better way to update parameters during preview please share.

Related

Camera preview is dark using Camera2

I am trying to use Camera2 to allow an app to take a simple picture. I managed to get a working sample using android-Camera2Basic sample code, the problem is that the camera preview is very dark (same problem as this other question), following some answers i did get a proper FPS range [15, 15], setting this in the lockFocus() method allows the app to great a clear picture with correct brightness and fixes the preview from the camera:
private void lockFocus() {
try {
// This is how to tell the camera to lock focus.
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_START);
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, Range.create(15, 15));
// Tell #mCaptureCallback to wait for the lock.
mState = STATE_WAITING_LOCK;
mCaptureSession.capture(mPreviewRequestBuilder.build(), mCaptureCallback, mBackgroundHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
But the preview before taking the pic is still very dark. I tried to set up the same line of code in other parts of the sample but is not working. How can i fix it in order to ge the same results in the preview? I am working with a Samsung SM-P355M tablet.
Using an FPS range with equal lower and upper bounds, such as [15,15], [30,30], [etc...], will put a constrain to the AE algorithm about how much it can adjust to light changes, which therefore may produce dark results. Such type of ranges are meant for video recording to maintain a constant FPS. For photos you need to find a range with a wide spread between the lower and upper bound, such as [7,30], [15,25], [etc...]
The next method can help you to find the optimal FPS range. Take into account that it is meant for photos and not video recording as it discards FPS ranges with equal lower and upper bounds.
(Adjust MIN_FPS_RANGE and MAX_FPS_RANGE to your requirements)
#Nullable
public static Range<Integer> getOptimalFpsRange(#NonNull final CameraCharacteristics characteristics) {
final int MIN_FPS_RANGE = 0;
final int MAX_FPS_RANGE = 30;
final Range<Integer>[] rangeList = characteristics.get(CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES);
if ((rangeList == null) || (rangeList.length == 0)) {
Log.e(TAG, "Failed to get FPS ranges.");
return null;
}
Range<Integer> result = null;
for (final Range<Integer> entry : rangeList) {
int candidateLower = entry.getLower();
int candidateUpper = entry.getUpper();
if (candidateUpper > 1000) {
Log.w(TAG,"Device reports FPS ranges in a 1000 scale. Normalizing.");
candidateLower /= 1000;
candidateUpper /= 1000;
}
// Discard candidates with equal or out of range bounds
final boolean discard = (candidateLower == candidateUpper)
|| (candidateLower < MIN_FPS_RANGE)
|| (candidateUpper > MAX_FPS_RANGE);
if (discard == false) {
// Update if none resolved yet, or the candidate
// has a >= upper bound and spread than the current result
final boolean update = (result == null)
|| ((candidateUpper >= result.getUpper()) && ((candidateUpper - candidateLower) >= (result.getUpper() - result.getLower())));
if (update == true) {
result = Range.create(candidateLower, candidateUpper);
}
}
}
return result;
}
After lots of reseach it seams there is no easy fix for this (at least not with our same hardware), so we implemented a new version of the camera activities this time using the deprecated Camera Api and everything works as espected. Not really a clean solution but so far works for me.

Android Camera2 increase brightness

I am using android camera2 in my application to take continuous images, Here when I use camera2 getting image preview brightness very dark compare to original camera. I seen this but there is no similar requirement in that answer.
I tried to set brightness in camera2 as suggested here:
Note that this control will only be effective if android.control.aeMode != OFF. This control will take effect even when android.control.aeLock == true.
captureRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
captureRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON);
captureRequestBuilder.set(CaptureRequest.CONTROL_AE_LOCK, true);
captureRequestBuilder.set(CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION, 6);
But it still showing preview as dark image only as shown below.
See the difference here:
Original Camera:
Using Camera2:
And what is the value I need to pass as second parameter in:
captureRequestBuilder.set(CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION, 6);
I kept 6 because as suggested in doc's:
For example, if the exposure value (EV) step is 0.333, '6' will mean an exposure compensation of +2 EV; -3 will mean an exposure compensation of -1 EV.
But still no effect in brightness..
Here it is:
Add below code in onConfigured() and unlockFocus()
captureRequestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE,getRange());
By using the above code you will get the better preview. But your captured picture will remain as it is. To get the better picture as well use the same below code in captureStillPicture()
captureBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, getRange());
getRange is:
private Range<Integer> getRange() {
CameraCharacteristics chars = null;
try {
chars = mCameraManager.getCameraCharacteristics(mCameraId);
Range<Integer>[] ranges = chars.get(CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES);
Range<Integer> result = null;
for (Range<Integer> range : ranges) {
int upper = range.getUpper();
// 10 - min range upper for my needs
if (upper >= 10) {
if (result == null || upper < result.getUpper().intValue()) {
result = range;
}
}
}
if (result == null) {
result = ranges[0];
}
return result;
} catch (CameraAccessException e) {
e.printStackTrace();
return null;
}
}
CONTROL_AE_LOCK should be off. You have misinterpreted the doc, possibly document itself is a bit confusing.
Note that this control will only be effective if
android.control.aeMode != OFF. This control will take effect even when
android.control.aeLock == true.
What it means is that when AE lock is ON, the exposure compensation will be applied on the locked exposure and not on the instantaneous exposure at the time of taking picture.
Even in your repeat request, exposure is locked so it doesn't help.
Remove AE lock and it should work.
While setting CONTROL_AE_EXPOSURE_COMPENSATION the second parameter as defined by docs is relative to CameraCharacteristics.CONTROL_AE_COMPENSATION_STEP
The adjustment is measured as a count of steps, with the step size defined by android.control.aeCompensationStep and the allowed range by android.control.aeCompensationRange."
The value of 6 in the example for +2EV is correct only when step is 0.333 which is just an example.
Following code will give you the exposure compensation value to be used for +2EV
CameraManager manager = (CameraManager)this.getSystemService(Context.CAMERA_SERVICE);
CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId);
double exposureCompensationSteps = characteristics.get(CameraCharacteristics.CONTROL_AE_COMPENSATION_STEP).doubleValue();
int exposureCompensation = (int)( 2.0 / exposureCompensationSteps );
I would also suggest you check if the value is within the range specified by CameraCharacteristics.CONTROL_AE_COMPENSATION_RANGE
You can try this
public void setBrightness(int value) {
int brightness = (int) (minCompensationRange + (maxCompensationRange - minCompensationRange) * (value / 100f));
previewRequestBuilder.set(CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION, brightness);
applySettings();
}
private void applySettings() {
captureSession.setRepeatingRequest(previewRequestBuilder.build(), null, null);
}
I messed around with CaptureRequest.SENSOR_SENSITIVITY and it worked great on my Samsung s3, s7 and s8 phones.
You can get the CameraCharacteristics.SENSOR_INFO_SENSITIVITY_RANGE
sensitivity_range = chars.get(CameraCharacteristics.SENSOR_INFO_SENSITIVITY_RANGE);
On my s7, the range is from mid 50s to more than 3000. I then set it to 1500 as follows.
mCaptureRequest.set(CaptureRequest.SENSOR_SENSITIVITY, 1500);
It brightened the preview a few factors.
First, don't lock autoexposure - that's not needed when adjusting exposure compensation.
Second, did you call CameraCaptureSession.setRepeatingRequest with your new capture request?

Low quality picture and what's the usage of overriding surfaceChanged when using Android Camera API

I have a problem for my application, specifically, when I want to take a picture (from android Camera API) and send it to my server, I got some strange pictures like following example:
which contain many noise points and actually the size/resolution are very small (176*144 pixels). And this is my original code for surfaceChanged:
public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
// start preview with new settings
try {
Camera.Parameters parameters = mCamera.getParameters();
parameters.set("orientation", "portrait");
mCamera.setDisplayOrientation(90);
mCamera.setPreviewDisplay(mHolder);
mCamera.startPreview();
} catch (Exception e){
Log.e(LOG_TAG, "Error starting camera preview: " + e.getMessage());
}
mCamera.startPreview();
}
And I tried to ask some classmates for this issue, they don't know but one of them give the following code:
#Override
public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) {
try {
// camera.stopPreview();
// Camera.Parameters mParameters = camera.getParameters();
Camera.Parameters parameters = mCamera.getParameters();
Camera.Size optiSize = getBestPreviewSize(720, 720);
if (optiSize != null) {
parameters.setPreviewSize(optiSize.width, optiSize.height);
parameters.setPictureSize(optiSize.width, optiSize.height);
}
parameters.set("orientation", "portrait");
mCamera.setDisplayOrientation(90);
List<String> focusModes = parameters.getSupportedFocusModes();
if (focusModes.contains(Camera.Parameters.FOCUS_MODE_AUTO)) {
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
}
mCamera.setParameters(parameters);
// mParameters.setFlashMode(Camera.Parameters.FLASH_MODE_TORCH);
// camera.setParameters(mParameters); // apply the changes
} catch (Exception e) {
// older phone - doesn't support these calls
}
mCamera.startPreview();
}
private Camera.Size getBestPreviewSize(int width, int height) {
List<Camera.Size> sizes = mCamera.getParameters()
.getSupportedPreviewSizes();
if (sizes == null)
return null;
Camera.Size optimalSize = null;
int tmpSize;
int minWidthDiff = 1000;
for (Camera.Size size : sizes) {
if (size.width > size.height)
tmpSize = size.height;
else
tmpSize = size.width;
if (Math.abs(tmpSize - width) < minWidthDiff) {
minWidthDiff = Math.abs(tmpSize - width);
optimalSize = size;
}
}
return optimalSize;
}
And this one works pretty well, it can store the original picture with the full resolution. Although I modify a little bit of my other codes to make the new codes compatible with my system, (add AutoFocus, for example). But I think the problem occurs because the method surfaceChanged because if I take image only with Autofocus, it still not work.
Therefore my question is: why this method will influence the quality of my image. I thought this method is only called when we "change" the surface, something like rotate the screen. But apparently it do something more than that?
Can anybody give me some help? Or some posts to explain this fact? Thank you very much in advance.
This method is called when the surface is changed, e.g. it is resized. It is common to adjust some of the camera's parameters to better fit the new configurations (such as setting an appropriate preview size).
What you are doing in the first code is you're getting the camera's Camera.Parameters, set a key-value pair (by the way, the documentation does not mention any parameter with the key "orientation" - it probably has no meaning). What you forgot to do is to apply the new parameters to the camera using Camera.setParameters(). Then again, without actually setting any valid parameters, this would have no effect.
This leads to the low-quality picture issue. You should use the Camera.Parameters object to set a desired preview and picture size. In the second block of code, this is done inside getBestPreviewSize(). A list of available preview sizes is got and the most fitting one is chosen based on the preview's size. It might be helpful to set a satisfactory picture size, too.
The thing is, different devices have different set of supported values for the parameters. If you want to provide a consistent functionality, you should check the supported values using Camera.Parameters.getSupported* methods and set them accordingly.

Camera preview processing on Android

I'm making a line follower for my robot on Android (to learn Java/Android programming), currently I'm facing the image processing problem: the camera preview returns an image format called YUV which I want to convert to a threshold in order to know where the line is, how would one do that?
As of now I've succeeded getting something, that is I definitely can read data from the camera preview and by some miracle even know if the light intensity is over or under a certain value at a certain area on the screen. My goal is to draw the robot's path on an overlay over the camera's preview, that too works to some extent, but the problem is the YUV management.
As you can see not only the dark area is drawn sideways, but it also repeats itself 4 times and the preview image is stretched, I cannot figure out how to fix these problems.
Here's the relevant part of code:
public void surfaceCreated(SurfaceHolder arg0) {
// TODO Auto-generated method stub
// camera setup
mCamera = Camera.open();
Camera.Parameters parameters = mCamera.getParameters();
List<Camera.Size> sizes = parameters.getSupportedPreviewSizes();
for(int i=0; i<sizes.size(); i++)
{
Log.i("CS", i+" - width: "+sizes.get(i).width+" height: "+sizes.get(i).height+" size: "+(sizes.get(i).width*sizes.get(i).height));
}
// change preview size
final Camera.Size cs = sizes.get(8);
parameters.setPreviewSize(cs.width, cs.height);
// initialize image data array
imgData = new int[cs.width*cs.height];
// make picture gray scale
parameters.setColorEffect(Camera.Parameters.EFFECT_MONO);
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
mCamera.setParameters(parameters);
// change display size
LayoutParams params = (LayoutParams) mSurfaceView.getLayoutParams();
params.height = (int) (mSurfaceView.getWidth()*cs.height/cs.width);
mSurfaceView.setLayoutParams(params);
LayoutParams overlayParams = (LayoutParams) swOverlay.getLayoutParams();
overlayParams.width = mSurfaceView.getWidth();
overlayParams.height = mSurfaceView.getHeight();
swOverlay.setLayoutParams(overlayParams);
try
{
mCamera.setPreviewDisplay(mSurfaceHolder);
mCamera.setDisplayOrientation(90);
mCamera.startPreview();
}
catch (IOException e)
{
e.printStackTrace();
mCamera.stopPreview();
mCamera.release();
}
// callback every time a new frame is available
mCamera.setPreviewCallback(new PreviewCallback() {
public void onPreviewFrame(byte[] data, Camera camera)
{
// create bitmap from camera preview
int pixel, pixVal, frameSize = cs.width*cs.height;
for(int i=0; i<frameSize; i++)
{
pixel = (0xff & ((int) data[i])) - 16;
if(pixel < threshold)
{
pixVal = 0;
}
else
{
pixVal = 1;
}
imgData[i] = pixVal;
}
int cp = imgData[(int) (cs.width*(0.5+(cs.height/2)))];
//Log.i("CAMERA", "Center pixel RGB: "+cp);
debug.setText("Center pixel: "+cp);
// process preview image data
Paint paint = new Paint();
paint.setColor(Color.YELLOW);
int start, finish, last;
start = finish = last = -1;
float x_ratio = mSurfaceView.getWidth()/cs.width;
float y_ratio = mSurfaceView.getHeight()/cs.height;
// display calculated path on overlay using canvas
Canvas overlayCanvas = overlayHolder.lockCanvas();
overlayCanvas.drawColor(0, Mode.CLEAR);
// start by finding the tape from bottom of the screen
for(int y=cs.height; y>0; y--)
{
for(int x=0; x<cs.width; x++)
{
pixel = imgData[y*cs.height+x];
if(pixel == 1 && last == 0 && start == -1)
{
start = x;
}
else if(pixel == 0 && last == 1 && finish == -1)
{
finish = x;
break;
}
last = pixel;
}
//overlayCanvas.drawLine(start*x_ratio, y*y_ratio, finish*x_ratio, y*y_ratio, paint);
//start = finish = last = -1;
}
overlayHolder.unlockCanvasAndPost(overlayCanvas);
}
});
}
This code generates an error sometimes when quitting the application due to some method being called after release, which is the least of my problems.
UPDATE:
Now that the orientation problem is fixed (CCD sensor orientation) I'm still facing the repetition problem, this is probably related to my YUV data management...
Your surface and camera management looks correct, but I would doublecheck that camera actually accepted preview size settings ( some camera implementations reject some settings silently)
As you are working with portrait mode, you have to keep in mind that camera does not give a fart about prhone orientation - its coordinate origin isdetermined by CCD chip and is always to right corner and scan direction is from top to bottom and right to left - quite different from your overlay canvas. ( But if you are in landscape mode, everything is correct ;) ) - this is certaily source of odd drawing result
Your threshloding is bit naive and not very usefull in real life - I would suggest adaptive threshloding. In our javaocr project ( pure java, also has android demos ) we implemented efficient sauvola binarisation (see demos):
http://sourceforge.net/projects/javaocr/
Performance binarisation can be improved to work only on single image rows (patches welcome)
Issue with UV part of image is easy - default forman is NV21, luminance comes first
and this is just byte stream, and you do not need UV part of image at all (look into demos above)

RuntimeException on Camera.setParameters() on nexus one

I copied the code from the answer here and I still am getting a RuntimeException: setParameters failed error on my nexus one. My manifest file has camera and wake_lock permissions. This works on the emulator, and on the droid I don't get the error but it does have a rotation problem.
You're most likely requsting an invalid preview size. If you check the results of adb logcat you'll probably see something like this:
E/QualcommCameraHardware(22732): Invalid preview size requested: 480x724
The solution is to request the closest available preview size to the one you'd like; you can get a list of available preview sizes by calling getSupportedPreviewSizes in the Camera.Parameters object returned by Camera.getParameters.
I corrected this by doing what Roman said, with the code:
Camera.Parameters parameters = camera.getParameters();
List<Camera.Size> sizes = parameters.getSupportedPreviewSizes();
Camera.Size cs = sizes.get(0);
parameters.setPreviewSize(cs.width, cs.height);
camera.setParameters(parameters);
For what it's worth, the source of my issue ended up being that I was trying to call parameters.setFlashMode(Camera.Parameters.FLASH_MODE_OFF); without first verifying that flash modes were supported by checking that parameters.getFlashMode() != null.
There's more than one cause for this poorly documented exception, so check all of your parameters and not just that you're using a supportedPreviewSize.
None of the above solved this for me. Adding this code before setting the parameters did though.
// stop preview before making changes
try {
mCamera.stopPreview();
} catch (Exception e){
// ignore: tried to stop a non-existent preview
}
//now set your parameters
For me this would happen after taking a photo and the preview would freeze, until I updated my call for parameters to be the following. It is always important with this error to make sure you check all of the parameters that the camera is asking to set to make sure that every parameter you are asking the camera to set itself to is possible for the camera.
Camera.Parameters parameters = myCamera.getParameters();
With the preview size:
if (myCamera.getParameters().getSupportedPreviewSizes() != null){
Camera.Size previewSize = getOptimalPreviewSize(myCamera.getParameters().getSupportedPreviewSizes(), width, height);;
parameters.setPreviewSize(previewSize.width, previewSize.height);
}
With the flash/focus modes:
if(parameters.getSupportedFocusModes() != null && parameters.getSupportedFocusModes().contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)){
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
}
if (parameters.getSupportedFlashModes() != null && parameters.getSupportedFlashModes().contains(Camera.Parameters.FLASH_MODE_AUTO)){
parameters.setFlashMode(Camera.Parameters.FLASH_MODE_AUTO);
}
myCamera.setParameters(parameters);
etc. All of this wrapped in a nice try{}catch(){} works great. Good luck.
Here is the getOptimalPreview Size from this great tutorial:
private Camera.Size getOptimalPreviewSize(List<Camera.Size> sizes, int width, int height)
{
// Source: http://stackoverflow.com/questions/7942378/android-camera-will-not-work-startpreview-fails
Camera.Size optimalSize = null;
final double ASPECT_TOLERANCE = 0.1;
double targetRatio = (double) height / width;
// Try to find a size match which suits the whole screen minus the menu on the left.
for (Camera.Size size : sizes){
if (size.height != width) continue;
double ratio = (double) size.width / size.height;
if (ratio <= targetRatio + ASPECT_TOLERANCE && ratio >= targetRatio - ASPECT_TOLERANCE){
optimalSize = size;
}
}
// If we cannot find the one that matches the aspect ratio, ignore the requirement.
if (optimalSize == null) {
// TODO : Backup in case we don't get a size.
}
return optimalSize;
}
the solution from Sam is correct but the output image is still zoomed a little bit on several tablet devices. One of the best practices that I found on Internet, we should set in Camera host so that the properties will be re-used each time the camera is resumed. Here is implemented method in CameraHost:
#Override
public Parameters adjustPreviewParameters(Parameters parameters) {
List<Camera.Size> sizes = parameters.getSupportedPreviewSizes();
Camera.Size cs = sizes.get(0);
parameters.setPreviewSize(cs.width, cs.height);
return super.adjustPreviewParameters(parameters);
}
Some open source camera project like opencamera always use try-catch to call Camera.setParameters:
private void setCameraParameters(Camera.Parameters parameters) {
if( MyDebug.LOG )
Log.d(TAG, "setCameraParameters");
try {
camera.setParameters(parameters);
if( MyDebug.LOG )
Log.d(TAG, "done");
} catch (RuntimeException e) {
// just in case something has gone wrong
if( MyDebug.LOG )
Log.d(TAG, "failed to set parameters");
e.printStackTrace();
count_camera_parameters_exception++;
}
}
in addition,always get the latest getParameters before you call setParameters like this:
void setRotation(int rotation) {
Camera.Parameters parameters = this.getParameters();
parameters.setRotation(rotation);
setCameraParameters(parameters);
}

Categories

Resources