Difference between revisions of "Example Android Gradient"

From BoofCV
Jump to navigationJump to search
m
m
 
(2 intermediate revisions by the same user not shown)
Line 4: Line 4:
</gallery>
</gallery>
</center>
</center>
<center><SPAN STYLE="font-size: 20pt">New projects should use [[Example_Android_Fragment_Gradient | Fragments]]</SPAN></center>
<br>


Demonstration of how to capture and process a video stream in real-time using BoofCV on an Android device.  On Android devices, video streams are accessed inside a camera preview, which require several hoops to be jumped through.  What this example does is capture the image in NV21 format, convert it into an GrayU8, compute the image gradient, visualize the gradient in a Bitmap image, and display the results.  Note that the example below is not entirely self contained, see the complete project for additional files.
Demonstration of how to capture and process a video stream in real-time using BoofCV on an Android device.  On Android devices, video streams are accessed inside a camera preview, which require several hoops to be jumped through.  What this example does is capture the image in NV21 format, convert it into an GrayU8, compute the image gradient, visualize the gradient in a Bitmap image, and display the results.  Note that the example below is not entirely self contained, see the complete project for additional files.


Example File: [https://github.com/lessthanoptimal/BoofCV/blob/v0.27/integration/boofcv-android/examples/video/app/src/main/java/org/boofcv/video/VideoActivity.java VideoActivity.java]
Example File: [https://github.com/lessthanoptimal/BoofCV/blob/v0.31/integration/boofcv-android/examples/video/app/src/main/java/org/boofcv/video/GradientActivity.java GradientActivity.java]


Complete Project: [https://github.com/lessthanoptimal/BoofCV/tree/v0.27/integration/boofcv-android/examples/video Android Project]
Complete Project: [https://github.com/lessthanoptimal/BoofCV/blob/v0.31/integration/boofcv-android/examples/video/ Android Project]


Concepts:
Concepts:
Line 25: Line 29:
<syntaxhighlight lang="java">
<syntaxhighlight lang="java">
/**
/**
  * Demonstration of how to process a video stream on an Android device using BoofCVMost of the drudgery of
  * Demonstrates how to use the visualize activity. A video stream is opened and the image gradient
  * video processing is handled by {@link VideoDisplayActivity}. This class still needs to tell it which
* is found. The gradient is then rendered into a format which can be visualized and displayed
  * camera to use and needs to select the optimal resolution. The actual processing is done by {@link ShowGradient}
* on the Android device's screen.
  * which is passed into the super class when {@link #onResume()} is called.
*
  * This greatly simplifies the process of capturing and visualizing image data from a camera.
  * Internally it uses the camera 2 API. You can customize its behavior by overriding
* different internal functions. For more details, see the JavaDoc of it's parent classes.
  *
  * @see VisualizeCamera2Activity
  * @see boofcv.android.camera2.SimpleCamera2Activity
  *
  *
  * @author Peter Abeles
  * @author Peter Abeles
  */
  */
public class VideoActivity extends VideoDisplayActivity
public class GradientActivity extends VisualizeCamera2Activity
{
{
@Override
// Storage for the gradient
protected void onResume() {
private GrayS16 derivX = new GrayS16(1,1);
super.onResume();
private GrayS16 derivY = new GrayS16(1,1);
setProcessing( new ShowGradient());
 
// Storage for image gradient. In general you will want to precompute data structures due
// to the expense of garbage collection
private ImageGradient<GrayU8,GrayS16> gradient = FactoryDerivative.three(GrayU8.class, GrayS16.class);
 
// Used to display text info on the display
private Paint paintText = new Paint();


// for fun you can display the FPS by uncommenting the line below.
public GradientActivity() {
// The FPS will vary depending on processing time and shutter speed,
// The default behavior for selecting the camera's resolution is to
// which is dependent on lighting conditions
// find the resolution which comes the closest to having this many
// setShowFPS(true);
// pixels.
targetResolution = 640*480;
}
}


@Override
@Override
protected Camera openConfigureCamera( Camera.CameraInfo cameraInfo )
protected void onCreate(Bundle savedInstanceState) {
{
super.onCreate(savedInstanceState);
Camera mCamera = selectAndOpenCamera(cameraInfo);
Camera.Parameters param = mCamera.getParameters();


// Select the preview size closest to 320x240
setContentView(R.layout.gradient);
// Smaller images are recommended because some computer vision operations are very expensive
FrameLayout surface = findViewById(R.id.camera_frame);
List<Camera.Size> sizes = param.getSupportedPreviewSizes();
Camera.Size s = sizes.get(closest(sizes,320,240));
param.setPreviewSize(s.width,s.height);
mCamera.setParameters(param);


return mCamera;
// By calling this function you are telling the camera library that you wish to process
// images in a gray scale format. The video stream is typically in YUV420. Color
// image formats are supported as RGB, YUV, ... etc, color spaces.
setImageType(ImageType.single(GrayU8.class));
 
// Configure paint used to display FPS
paintText.setStrokeWidth(4*displayMetrics.density);
paintText.setTextSize(14*displayMetrics.density);
paintText.setTextAlign(Paint.Align.LEFT);
paintText.setARGB(0xFF,0xFF,0xB0,0);
paintText.setTypeface(Typeface.create(Typeface.MONOSPACE, Typeface.BOLD));
 
// The camera stream will now start after this function is called.
startCamera(surface,null);
}
}


/**
/**
* Step through the camera list and select a camera. It is also possible that there is no camera.
* This is where you specify custom camera settings. See {@link boofcv.android.camera2.SimpleCamera2Activity}'s
* The camera hardware requirement in AndroidManifest.xml was turned off so that devices with just
* JavaDoc for more funcitons which you can override.
* a front facing camera can be found.  Newer SDK's handle this in a more sane way, but with older devices
*
* you need this work around.
* @param captureRequestBuilder Used to configure the camera.
*/
*/
private Camera selectAndOpenCamera(Camera.CameraInfo info) {
@Override
int numberOfCameras = Camera.getNumberOfCameras();
protected void configureCamera(CameraDevice device, CameraCharacteristics characteristics, CaptureRequest.Builder captureRequestBuilder) {
captureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_VIDEO);
captureRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON);
}


int selected = -1;
/**
* During camera initialization this function is called once after the resolution is known.
* This is a good function to override and predeclare data structres which are dependent
* on the video feeds resolution.
*/
@Override
protected void onCameraResolutionChange( int width , int height, int sensorOrientation ) {
super.onCameraResolutionChange(width, height,sensorOrientation);


for (int i = 0; i < numberOfCameras; i++) {
derivX.reshape(width, height);
Camera.getCameraInfo(i, info);
derivY.reshape(width, height);
}


if( info.facing == Camera.CameraInfo.CAMERA_FACING_BACK ) {
/**
selected = i;
* This function is invoked in its own thread and can take as long as you want.
break;
*/
} else {
@Override
// default to a front facing camera if a back facing one can't be found
protected void processImage(ImageBase image) {
selected = i;
// The data type of 'image' was specified in onCreate() function
}
// The line below will compute the gradient and store it in two images. One for the
}
// gradient along the x-axis and the other along the y-axis
 
gradient.process((GrayU8)image,derivX,derivY);
if( selected == -1 ) {
dialogNoCamera();
return null; // won't ever be called
} else {
return Camera.open(selected);
}
}
}


/**
/**
* Gracefully handle the situation where a camera could not be found
* Override the default behavior and colorize gradient instead of converting input image.
*/
*/
private void dialogNoCamera() {
@Override
AlertDialog.Builder builder = new AlertDialog.Builder(this);
protected void renderBitmapImage(BitmapMode mode, ImageBase image) {
builder.setMessage("Your device has no cameras!")
switch( mode ) {
.setCancelable(false)
case UNSAFE: { // this application is configured to use double buffer and could ignore all other modes
.setPositiveButton("OK", new DialogInterface.OnClickListener() {
VisualizeImageData.colorizeGradient(derivX, derivY, -1, bitmap, bitmapTmp);
public void onClick(DialogInterface dialog, int id) {
} break;
System.exit(0);
 
case DOUBLE_BUFFER: {
VisualizeImageData.colorizeGradient(derivX, derivY, -1, bitmapWork, bitmapTmp);
 
if( bitmapLock.tryLock() ) {
try {
Bitmap tmp = bitmapWork;
bitmapWork = bitmap;
bitmap = tmp;
} finally {
bitmapLock.unlock();
}
}
});
}
AlertDialog alert = builder.create();
} break;
alert.show();
}
}
}


/**
/**
* Goes through the size list and selects the one which is the closest specified size
* Demonstrates how to draw visuals
*/
*/
public static int closest( List<Camera.Size> sizes , int width , int height ) {
@Override
int best = -1;
protected void onDrawFrame(SurfaceView view, Canvas canvas) {
int bestScore = Integer.MAX_VALUE;
super.onDrawFrame(view, canvas);


for( int i = 0; i < sizes.size(); i++ ) {
// Display info on the image being process and how fast input camera
Camera.Size s = sizes.get(i);
// stream (probably in YUV420) is converted into a BoofCV format
 
int width = bitmap.getWidth();
int dx = s.width-width;
int height = bitmap.getHeight();
int dy = s.height-height;
canvas.drawText(String.format(Locale.getDefault(),
 
"%d x %d Convert: %4.1f (ms)",
int score = dx*dx + dy*dy;
width,height,periodConvert.getAverage()),
if( score < bestScore ) {
0,120,paintText);
best = i;
bestScore = score;
}
}


return best;
// Pro tip: Run in app fast or release mode for a dramatic speed up!
// In Android Studio expand "Build Variants" tab on left.
}
}
}
}
</syntaxhighlight>
</syntaxhighlight>

Latest revision as of 14:23, 15 July 2023


New projects should use Fragments


Demonstration of how to capture and process a video stream in real-time using BoofCV on an Android device. On Android devices, video streams are accessed inside a camera preview, which require several hoops to be jumped through. What this example does is capture the image in NV21 format, convert it into an GrayU8, compute the image gradient, visualize the gradient in a Bitmap image, and display the results. Note that the example below is not entirely self contained, see the complete project for additional files.

Example File: GradientActivity.java

Complete Project: Android Project

Concepts:

  • Android
  • Camera Preview
  • Image Gradient

Related Tutorial:

Related Examples:

Example Code

/**
 * Demonstrates how to use the visualize activity. A video stream is opened and the image gradient
 * is found. The gradient is then rendered into a format which can be visualized and displayed
 * on the Android device's screen.
 *
 * This greatly simplifies the process of capturing and visualizing image data from a camera.
 * Internally it uses the camera 2 API. You can customize its behavior by overriding
 * different internal functions. For more details, see the JavaDoc of it's parent classes.
 *
 * @see VisualizeCamera2Activity
 * @see boofcv.android.camera2.SimpleCamera2Activity
 *
 * @author Peter Abeles
 */
public class GradientActivity extends VisualizeCamera2Activity
{
	// Storage for the gradient
	private GrayS16 derivX = new GrayS16(1,1);
	private GrayS16 derivY = new GrayS16(1,1);

	// Storage for image gradient. In general you will want to precompute data structures due
	// to the expense of garbage collection
	private ImageGradient<GrayU8,GrayS16> gradient = FactoryDerivative.three(GrayU8.class, GrayS16.class);

	// Used to display text info on the display
	private Paint paintText = new Paint();

	public GradientActivity() {
		// The default behavior for selecting the camera's resolution is to
		// find the resolution which comes the closest to having this many
		// pixels.
		targetResolution = 640*480;
	}

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);

		setContentView(R.layout.gradient);
		FrameLayout surface = findViewById(R.id.camera_frame);

		// By calling this function you are telling the camera library that you wish to process
		// images in a gray scale format. The video stream is typically in YUV420. Color
		// image formats are supported as RGB, YUV, ... etc, color spaces.
		setImageType(ImageType.single(GrayU8.class));

		// Configure paint used to display FPS
		paintText.setStrokeWidth(4*displayMetrics.density);
		paintText.setTextSize(14*displayMetrics.density);
		paintText.setTextAlign(Paint.Align.LEFT);
		paintText.setARGB(0xFF,0xFF,0xB0,0);
		paintText.setTypeface(Typeface.create(Typeface.MONOSPACE, Typeface.BOLD));

		// The camera stream will now start after this function is called.
		startCamera(surface,null);
	}

	/**
	 * This is where you specify custom camera settings. See {@link boofcv.android.camera2.SimpleCamera2Activity}'s
	 * JavaDoc for more funcitons which you can override.
	 *
	 * @param captureRequestBuilder Used to configure the camera.
	 */
	@Override
	protected void configureCamera(CameraDevice device, CameraCharacteristics characteristics, CaptureRequest.Builder captureRequestBuilder) {
		captureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_VIDEO);
		captureRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON);
	}

	/**
	 * During camera initialization this function is called once after the resolution is known.
	 * This is a good function to override and predeclare data structres which are dependent
	 * on the video feeds resolution.
	 */
	@Override
	protected void onCameraResolutionChange( int width , int height, int sensorOrientation ) {
		super.onCameraResolutionChange(width, height,sensorOrientation);

		derivX.reshape(width, height);
		derivY.reshape(width, height);
	}

	/**
	 * This function is invoked in its own thread and can take as long as you want.
	 */
	@Override
	protected void processImage(ImageBase image) {
		// The data type of 'image' was specified in onCreate() function
		// The line below will compute the gradient and store it in two images. One for the
		// gradient along the x-axis and the other along the y-axis
		gradient.process((GrayU8)image,derivX,derivY);
	}

	/**
	 * Override the default behavior and colorize gradient instead of converting input image.
	 */
	@Override
	protected void renderBitmapImage(BitmapMode mode, ImageBase image) {
		switch( mode ) {
			case UNSAFE: { // this application is configured to use double buffer and could ignore all other modes
				VisualizeImageData.colorizeGradient(derivX, derivY, -1, bitmap, bitmapTmp);
			} break;

			case DOUBLE_BUFFER: {
				VisualizeImageData.colorizeGradient(derivX, derivY, -1, bitmapWork, bitmapTmp);

				if( bitmapLock.tryLock() ) {
					try {
						Bitmap tmp = bitmapWork;
						bitmapWork = bitmap;
						bitmap = tmp;
					} finally {
						bitmapLock.unlock();
					}
				}
			} break;
		}
	}

	/**
	 * Demonstrates how to draw visuals
	 */
	@Override
	protected void onDrawFrame(SurfaceView view, Canvas canvas) {
		super.onDrawFrame(view, canvas);

		// Display info on the image being process and how fast input camera
		// stream (probably in YUV420) is converted into a BoofCV format
		int width = bitmap.getWidth();
		int height = bitmap.getHeight();
		canvas.drawText(String.format(Locale.getDefault(),
				"%d x %d Convert: %4.1f (ms)",
				width,height,periodConvert.getAverage()),
				0,120,paintText);

		// Pro tip: Run in app fast or release mode for a dramatic speed up!
		// In Android Studio expand "Build Variants" tab on left.
	}
}