注:本文仅介绍Android端实现思路和代码 不提供相应的算法代码,所以如果要看算法代码,先劝退
公司原本的考勤打卡是通过外置摄像头进行打卡,不管是使用还是调试都有很大的局限性和不够便捷,而且因为是通过socket长链接进行数据实时更新,所以稳定性也较差。
部署人员反馈后,考虑使用一体机自带的前置摄像头实现人脸打卡功能。
方案一:实时推流给后台,通过算法对视频流就行识别,实现考勤(否决,对设备网络要求较高,实现后容错率极低)
方案二:考勤人员手动点击设备上的触屏按钮进行打卡(否决,需要人机交互,不够智能,且人不一定会配合)
方案三:开启设备的前置摄像头,定时捕获摄像头画面,传递图片给后台,然后通过算法识别实现打卡(通过)
确认好方案,接下来就是开发了。
首先需要开启Camera
private void openCamera() {
try {
if(mCamera!=null){
closeCamera();
}
mCamera = Camera.open();
mCamera.startPreview();
if (mCamera !=null) {
SurfaceHolder mHolder =surfaceView.getHolder();
mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
mHolder.addCallback(new CameraCallBack());
}
}catch (RuntimeException e){
Log.d("发生异常了",e.toString());
}
}
private class CameraCallBackimplements SurfaceHolder.Callback {
@Override
public void surfaceCreated(SurfaceHolder holder) {
try {
mCamera.setDisplayOrientation(0);
Camera.Parameters params =mCamera.getParameters();
params.setPreviewSize(640, 480);
List focusModes = params.getSupportedFocusModes();
if (focusModes.contains(Camera.Parameters.FOCUS_MODE_AUTO)) {
// set the focus mode
params.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
// set Camera parameters
mCamera.setParameters(params);
}
mCamera.setPreviewDisplay(holder);
final Camera.Size size =mCamera.getParameters().getPreviewSize();
previewHeight = size.height;
previewWidth = size.width;
}catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void surfaceChanged(final SurfaceHolder holder, int format, int width, int height) {
if(mCamera!=null){
mCamera.addCallbackBuffer(new byte[((previewWidth *previewHeight) * ImageFormat.getBitsPerPixel(ImageFormat.NV21)) /8]);
mCamera.setPreviewCallbackWithBuffer(new Camera.PreviewCallback() {
@Override
public void onPreviewFrame(byte[] bytes, Camera camera) {
count++;
if (null == bytes) {
camera.addCallbackBuffer(new byte[((previewWidth *previewHeight) * ImageFormat.getBitsPerPixel(ImageFormat.NV21)) /8]);
}else {
if (count %5 ==0) {
data1 = bytes;
}
camera.addCallbackBuffer(bytes);
}
if (count >100) {
count =0;
}
}
});
}
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
}
这一步是开启摄像头并将摄像头画面绘制在SufceView上
然后是启动摄像头数据处理线程。
public class FaceCompareRunnableimplements Runnable{
@Override
public void run() {
while (!threadExit) {
try {
if (data1 !=null) {
Bitmap bitmap = NV21ToBitmap.nv21ToBitmap(data1, previewWidth, previewHeight);
ByteArrayOutputStream bos =new ByteArrayOutputStream();
// bitmap = BitmapUtil.adjustPhotoRotation(bitmap,270);
bitmap.compress(Bitmap.CompressFormat.JPEG, 60, bos);
// BitmapUtil.saveBitmap(bitmap,3);
// tu.setImageBitmap(bitmap);
bitmap =bitmap.copy(Bitmap.Config.RGB_565,true);
FaceDetector faceDetector =new FaceDetector(bitmap.getWidth(),bitmap.getHeight(),1);
FaceDetector.Face[] faces =new FaceDetector.Face[1];
int haveface = faceDetector.findFaces(bitmap,faces);
if(haveface>0){
facecheckonworkattendance(bos.toByteArray());
}
bos.close();
}
Thread.sleep(1*1000);
}catch (Exception e) {
Log.d("报错了",e.toString());
e.printStackTrace();
}
}
}
}
我这需要讲解下,因为摄像头捕获到面前画面再绘制到SurfaceView上并不一定是正向的 所以如果后台算法只能识别正向图片,那么久需要我们使用代码把图片进行一个旋转,如果算法都能识别,那就无所谓了 我在这块吃过亏,所以记录一下,希望能帮助到大家
bitmap = BitmapUtil.adjustPhotoRotation(bitmap,270);
以下为相关方法
/**
* 旋转bitmap
* @param bm
* @param orientationDegree
* @return
*/
public static BitmapadjustPhotoRotation(Bitmap bm, final int orientationDegree) {
Matrix m =new Matrix();
m.setRotate(orientationDegree, (float) bm.getWidth() /2, (float) bm.getHeight() /2);
try {
Bitmap bm1 = Bitmap.createBitmap(bm, 0, 0, bm.getWidth(), bm.getHeight(), m, true);
return bm1;
}catch (OutOfMemoryError ex) {
}
return null;
}
还有就是需要对图片进行压缩,因为摄像头像素原因,图片可能会很快,如果网络如果不好,不压缩会上传得很慢,与考勤需要的效率相悖。所以最好还是压缩一下。
bitmap.compress(Bitmap.CompressFormat.JPEG, 60, bos);
然后是这段代码的解释:
bitmap =bitmap.copy(Bitmap.Config.RGB_565,true);
FaceDetector faceDetector =new FaceDetector(bitmap.getWidth(),bitmap.getHeight(),1);
FaceDetector.Face[] faces =new FaceDetector.Face[1];
int haveface = faceDetector.findFaces(bitmap,faces);
因为当时部门老大提了个要求 即要保证考勤的实时效率 也要保证服务器的压力不会太大(意思就是图中出现人脸才去请求服务器),因为如果一秒往后台传递一次图片,那么服务器虽说压力不大 但是不够智能优雅,所以重担又落在了客户端,这块代码是Android自带的算法,即识别图中是否存在人脸。如果有就去把图片传给后台。最后这是图片转换代码
/**
* 将NV21的图像数据转换为ARGB_4444的图像
* @param yv12
* @param width
* @param height
* @return
*/
public static Bitmapnv21ToBitmap(byte[] yv12, int width, int height) {
// byte[] nv21 = YV12toNV21(yv12,width,height);
if (yuvType ==null) {
yuvType =new Type.Builder(rs, Element.U8(rs)).setX(yv12.length);
in = Allocation.createTyped(rs, yuvType.create(), Allocation.USAGE_SCRIPT);
rgbaType =new Type.Builder(rs, Element.RGBA_8888(rs)).setX(width).setY(height);
out = Allocation.createTyped(rs, rgbaType.create(), Allocation.USAGE_SCRIPT);
}
in.copyFrom(yv12);
yuvToRgbIntrinsic.setInput(in);
yuvToRgbIntrinsic.forEach(out);
Bitmap bmpout = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_4444);
out.copyTo(bmpout);
return bmpout;
}
目前为止,功能就算做好了,如果有问题可以留言 剩下的就是把图片发送给后台进行识别了,上传图片的代码我就不贴了
if(haveface>0){
facecheckonworkattendance(bos.toByteArray());
}
这块代码就是上传
Thread.sleep(1*1000);这个的作用是让线程休眠一秒再去抓取图片
就这些了,谢谢观看,希望对你们有用
网友评论