添加相机功能
一个聪明的门铃应该捕获一个人(或什么)在门口的图像,允许所有者远程作出是否回答的决定。
因为Android Things 建立在Android框架上,您可以访问Android移动开发人员使用的强大的相机API。在本课程中,您将使用Android相机API访问摄像机外围设备,并捕获图像供以后处理。
连接相机
将支持的相机模块连接到电路板上的CSI-2相机端口。 确保电缆完全插入并均匀平放,然后再关闭 连接器门锁。
添加权限和所需功能
-
将所需的权限添加到应用的清单文件中:
<uses-permission android:name="android.permission.CAMERA" />
-
声明您的应用程序需要相机存在:
<uses-feature android:name="android.hardware.camera" /> <uses-feature android:name="android.hardware.camera.autofocus" />
设置一个I / O线程
与外围硬件通信将阻塞操作引入到应用程序的流中。为了避免阻塞应用程序的主线程,从而延迟框架事件,创建后台工作线程来处理输入和处理命令。 HandlerThread
非常适用于此目的。
public class DoorbellActivity extends Activity {
/**
* A Handler for running tasks in the background.
*/
private Handler mBackgroundHandler;
/**
* An additional thread for running tasks that shouldn't block the UI.
*/
private HandlerThread mBackgroundThread;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
startBackgroundThread();
}
@Override
protected void onDestroy() {
super.onDestroy();
mBackgroundThread.quitSafely();
}
/**
* Starts a background thread and its Handler.
*/
private void startBackgroundThread() {
mBackgroundThread = new HandlerThread("InputThread");
mBackgroundThread.start();
mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
}
}
初始化相机会话
捕获摄像机图像的第一步是发现硬件并打开设备连接。要连接到相机设备:
CameraManager
系统服务发现带有 getCameraIdList()
的可用摄像头设备的列表。ImageReader
实例来处理原始相机数据,并为您的应用程序生成JPEG编码的图像。读者不同步处理数据,并在图像准备就绪时调用提供的 OnImageAvailableListener
。openCamera()
打开与相应摄像机设备的连接。CameraDevice.StateCallback
报告通过 onOpened()
回调方法成功打开了摄像头。CameraDevice
,当它不用于释放系统资源:public class DoorbellCamera {
// Camera image parameters (device-specific)
private static final int IMAGE_WIDTH = ...;
private static final int IMAGE_HEIGHT = ...;
private static final int MAX_IMAGES = ...;
// Image result processor
private ImageReader mImageReader;
// Active camera device connection
private CameraDevice mCameraDevice;
// Active camera capture session
private CameraCaptureSession mCaptureSession;
// Initialize a new camera device connection
public void initializeCamera(Context context,
Handler backgroundHandler,
ImageReader.OnImageAvailableListener imageListener) {
// Discover the camera instance
CameraManager manager = (CameraManager) context.getSystemService(CAMERA_SERVICE);
String[] camIds = {};
try {
camIds = manager.getCameraIdList();
} catch (CameraAccessException e) {
Log.d(TAG, "Cam access exception getting IDs", e);
}
if (camIds.length < 1) {
Log.d(TAG, "No cameras found");
return;
}
String id = camIds[0];
// Initialize image processor
mImageReader = ImageReader.newInstance(IMAGE_WIDTH, IMAGE_HEIGHT,
ImageFormat.JPEG, MAX_IMAGES);
mImageReader.setOnImageAvailableListener(imageListener, backgroundHandler);
// Open the camera resource
try {
manager.openCamera(id, mStateCallback, backgroundHandler);
} catch (CameraAccessException cae) {
Log.d(TAG, "Camera access exception", cae);
}
}
// Callback handling devices state changes
private final CameraDevice.StateCallback mStateCallback =
new CameraDevice.StateCallback() {
@Override
public void onOpened(CameraDevice cameraDevice) {
mCameraDevice = cameraDevice;
}
...
};
// Close the camera resources
public void shutDown() {
if (mCameraDevice != null) {
mCameraDevice.close();
}
}
}
触发图像捕获
一旦相机设备连接处于活动状态,请创建一个 CameraCaptureSession
以便于来自硬件的图像请求。打开捕获会话:
createCaptureSession()
方法构建一个新的 CameraCaptureSession
实例。ImageReader
的表面。CameraCaptureSession.StateCallback
来报告会话的配置和活动时间。如果一切都成功,回调函数调用 onConfigured()
方法。public class DoorbellCamera {
...
public void takePicture() {
if (mCameraDevice == null) {
Log.w(TAG, "Cannot capture image. Camera not initialized.");
return;
}
// Here, we create a CameraCaptureSession for capturing still images.
try {
mCameraDevice.createCaptureSession(
Collections.singletonList(mImageReader.getSurface()),
mSessionCallback,
null);
} catch (CameraAccessException cae) {
Log.d(TAG, "access exception while preparing pic", cae);
}
}
// Callback handling session state changes
private final CameraCaptureSession.StateCallback mSessionCallback =
new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(CameraCaptureSession cameraCaptureSession) {
// When the session is ready, we start capture.
mCaptureSession = cameraCaptureSession;
triggerImageCapture();
}
@Override
public void onConfigureFailed(CameraCaptureSession cameraCaptureSession) {
Log.w(TAG, "Failed to configure camera");
}
};
}
您的应用程序现在可以使用活动的 CameraCaptureSession
从相机硬件请求图像数据。在捕获会话内开始图像捕获请求:
CaptureRequest
。要捕获单个静止图像,请使用 TEMPLATE_STILL_CAPTURE
参数。ImageReader
表面。capture()
方法在 CameraCaptureSession
上启动捕获请求。public class DoorbellCamera {
// Image result processor
private ImageReader mImageReader;
// Active camera device connection
private CameraDevice mCameraDevice;
// Active camera capture session
private CameraCaptureSession mCaptureSession;
...
private void triggerImageCapture() {
try {
final CaptureRequest.Builder captureBuilder =
mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
captureBuilder.addTarget(mImageReader.getSurface());
captureBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON);
mCaptureSession.capture(captureBuilder.build(), mCaptureCallback, null);
} catch (CameraAccessException cae) {
Log.d(TAG, "camera capture exception");
}
}
// Callback handling capture progress events
private final CameraCaptureSession.CaptureCallback mCaptureCallback =
new CameraCaptureSession.CaptureCallback() {
...
@Override
public void onCaptureCompleted(CameraCaptureSession session,
CaptureRequest request,
TotalCaptureResult result) {
if (session != null) {
session.close();
mCaptureSession = null;
Log.d(TAG, "CaptureSession closed");
}
}
};
}
处理图像结果
从您的活动中,当按下门铃按钮时,初始化相机并调用 takePicture()
方法。在捕获过程中,相机硬件将图像数据流传输到提供的 ImageReader
曲面,并调用带有结果的 OnImageAvailableListener
。
捕获完成后获取图像:
onImageAvailable()
方法的 ImageReader
获取最新的图像。getBuffer()
方法返回的缓冲区中将JPEG编码图像作为 byte []
检索:public class DoorbellActivity extends Activity {
/**
* Camera capture device wrapper
*/
private DoorbellCamera mCamera;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
mCamera = DoorbellCamera.getInstance();
mCamera.initializeCamera(this, mBackgroundHandler, mOnImageAvailableListener);
}
@Override
protected void onDestroy() {
super.onDestroy();
...
mCamera.shutDown();
}
private Button.OnButtonEventListener mButtonCallback =
new Button.OnButtonEventListener() {
@Override
public void onButtonEvent(Button button, boolean pressed) {
if (pressed) {
// Doorbell rang!
mCamera.takePicture();
}
}
};
// Callback to receive captured camera image data
private ImageReader.OnImageAvailableListener mOnImageAvailableListener =
new ImageReader.OnImageAvailableListener() {
@Override
public void onImageAvailable(ImageReader reader) {
// Get the raw image bytes
Image image = reader.acquireLatestImage();
ByteBuffer imageBuf = image.getPlanes()[0].getBuffer();
final byte[] imageBytes = new byte[imageBuf.remaining()];
imageBuf.get(imageBytes);
image.close();
onPictureTaken(imageBytes);
}
};
private void onPictureTaken(byte[] imageBytes) {
if (imageBytes != null) {
// ...process the captured image...
}
}
}