UvcCamera:
https://xie.infoq.cn/article/7e6a906f7096bb8e743ec2fc1
OpenCV
https://blog.csdn.net/switch_love_case/article/details/104137311
public class UserFaceActivity extends BaseActivity implements CameraDialog.CameraDialogParent {private static final String TAG = "UserFaceActivity";private final Object mSync = new Object();// for accessing USB and USB cameraprivate USBMonitor mUSBMonitor;private UVCCamera mUVCCamera;private SurfaceView surfaceView;private Surface mPreviewSurface;private boolean isActive, isPreview, isTakePhoto;View photoView;private CascadeClassifier classifier;Mat mFrameChain = new Mat(UVCCamera.DEFAULT_PREVIEW_HEIGHT + UVCCamera.DEFAULT_PREVIEW_HEIGHT/2 , UVCCamera.DEFAULT_PREVIEW_WIDTH, CvType.CV_8UC1);private int mAbsoluteFaceSize = 200;String code;String cur;// 手动装载openCV库文件,以保证手机无需安装OpenCV Manager 不加这里将导致无法初始化 级联分类器(开机闪退)static {System.loadLibrary("opencv_java3");}boolean isFinish;@Overrideprotected void onCreate(final Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.user_face);surfaceView = findViewById(R.id.user_face_surface_view);mPreviewSurface = surfaceView.getHolder().getSurface();code = getIntent().getStringExtra("code");((TextView)findViewById(R.id.face_code)).setText(code);surfaceView.getHolder().addCallback(new SurfaceHolder.Callback() {@Overridepublic void surfaceCreated(final SurfaceHolder holder) {Log.v(TAG, "surfaceCreated:");}@Overridepublic void surfaceChanged(final SurfaceHolder holder, final int format, final int width, final int height) {if ((width == 0) || (height == 0)) return;Log.v(TAG, "surfaceChanged:");mPreviewSurface = holder.getSurface();synchronized (mSync) {if (isActive && !isPreview && (mUVCCamera != null)) {mUVCCamera.setPreviewDisplay(mPreviewSurface);mUVCCamera.startPreview();isPreview = true;}}}@Overridepublic void surfaceDestroyed(final SurfaceHolder holder) {Log.v(TAG, "surfaceDestroyed:");synchronized (mSync) {if (mUVCCamera != null) {mUVCCamera.stopPreview();}isPreview = false;if(mPreviewSurface!=null) {mPreviewSurface.release();mPreviewSurface = null;}}}});mUSBMonitor = new USBMonitor(this, mOnDeviceConnectListener);initClassifier();findViewById(R.id.back).setOnClickListener(v -> {synchronized (mSync){if(isFinish) return;isFinish = true;isActive = isPreview = false;}new Thread(() -> {try {Thread.sleep(1000);finish();} catch (InterruptedException e) {e.printStackTrace();}}).start();});findViewById(R.id.take_photo).setOnClickListener(v -> {isTakePhoto = true;});photoView = findViewById(R.id.face_photo);initFaceList();findViewById(R.id.delete_photo).setOnClickListener(v -> deletePhoto());}// 初始化人脸级联分类器,必须先初始化private void initClassifier() {try {InputStream is = getResources().openRawResource(R.raw.lbpcascade_frontalface);File cascadeDir = getDir("cascade", Context.MODE_PRIVATE);File cascadeFile = new File(cascadeDir, "lbpcascade_frontalface.xml");FileOutputStream os = new FileOutputStream(cascadeFile);byte[] buffer = new byte[4096];int bytesRead;while ((bytesRead = is.read(buffer)) != -1) {os.write(buffer, 0, bytesRead);}is.close();os.close();classifier = new CascadeClassifier(cascadeFile.getAbsolutePath());} catch (Exception e) {e.printStackTrace();}}@Overrideprotected void onStart() {super.onStart();Log.v(TAG, "onStart:");synchronized (mSync) {if (mUSBMonitor != null) {mUSBMonitor.register();}}}@Overrideprotected void onStop() {Log.v(TAG, "onStop:");synchronized (mSync) {if (mUSBMonitor != null) {mUSBMonitor.unregister();}}super.onStop();}@Overrideprotected void onDestroy() {Log.v(TAG, "onDestroy:");clear();super.onDestroy();}void clear(){synchronized (mSync) {isActive = isPreview = false;if (mUVCCamera != null) {mUVCCamera.stopCapture();mUVCCamera.destroy();mUVCCamera = null;}if (mUSBMonitor != null) {mUSBMonitor.destroy();mUSBMonitor = null;}if (mPreviewSurface != null) {mPreviewSurface.release();mPreviewSurface = null;}if(classifier!=null){classifier.empty();classifier = null;}}}private final OnDeviceConnectListener mOnDeviceConnectListener = new OnDeviceConnectListener() {@Overridepublic void onAttach(final UsbDevice device) {Log.v(TAG, "onAttach:"+device.toString());//自动连接try {if(device.getProductId() == getCameraId(UserFaceActivity.this, mUSBMonitor)){mUSBMonitor.requestPermission(device);}}catch (Exception e){e.printStackTrace();}//Toast.makeText(FaceActivity.this, "USB_DEVICE_ATTACHED", Toast.LENGTH_SHORT).show();}@Overridepublic void onConnect(final UsbDevice device, final UsbControlBlock ctrlBlock, final boolean createNew) {Log.v(TAG, "onConnect:"+device.toString());//Toast.makeText(FaceActivity.this, "USB_DEVICE_Connect", Toast.LENGTH_SHORT).show();synchronized (mSync) {if (mUVCCamera != null) {mUVCCamera.destroy();mUVCCamera = null;}isActive = isPreview = false;}queueEvent(new Runnable() {@SuppressLint("SetTextI18n")@Overridepublic void run() {synchronized (mSync) {final UVCCamera camera = new UVCCamera();camera.open(ctrlBlock);Log.i(TAG, "supportedSize:" + camera.getSupportedSize());try {camera.setPreviewSize(UVCCamera.DEFAULT_PREVIEW_WIDTH, UVCCamera.DEFAULT_PREVIEW_HEIGHT, UVCCamera.FRAME_FORMAT_MJPEG);} catch (final IllegalArgumentException e) {try {// fallback to YUV modecamera.setPreviewSize(UVCCamera.DEFAULT_PREVIEW_WIDTH, UVCCamera.DEFAULT_PREVIEW_HEIGHT, UVCCamera.DEFAULT_PREVIEW_MODE);} catch (final IllegalArgumentException e1) {camera.destroy();return;}}Log.d(TAG, "Starting processing thread");//new Thread(new CameraWorker()).start();if (mPreviewSurface != null) {isActive = true;camera.setPreviewDisplay(mPreviewSurface);camera.startPreview();camera.setFrameCallback(frame -> {if(!isActive || !isPreview) return;//System.out.println("FrameCallback");byte[] bytes = new byte[frame.capacity()];frame.get(bytes);//System.out.println("相机数据长度: " +bytes.length);mFrameChain.put(0, 0, bytes);if (isTakePhoto) {takeFacePhoto(mFrameChain.submat(0, UVCCamera.DEFAULT_PREVIEW_HEIGHT, 0, UVCCamera.DEFAULT_PREVIEW_WIDTH));}}, UVCCamera.PIXEL_FORMAT_YUV420SP);isPreview = true;}synchronized (mSync) {mUVCCamera = camera;}}}}, 0);}@Overridepublic void onDisconnect(final UsbDevice device, final UsbControlBlock ctrlBlock) {Log.v(TAG, "onDisconnect:");// XXX you should check whether the comming device equal to camera device that currently usingqueueEvent(new Runnable() {@Overridepublic void run() {synchronized (mSync) {if (mUVCCamera != null) {isActive = isPreview = false;mUVCCamera.destroy();mUVCCamera = null;Log.d(TAG, "queueEvent: mUVCCamera.onDisconnect");}}}}, 0);}@Overridepublic void onDettach(final UsbDevice device) {Log.v(TAG, "onDettach:");//Toast.makeText(FaceActivity.this, "USB_DEVICE_DETACHED", Toast.LENGTH_SHORT).show();}@Overridepublic void onCancel(final UsbDevice device) {}};/*** to access from CameraDialog* @return*/@Overridepublic USBMonitor getUSBMonitor() {return mUSBMonitor;}@Overridepublic void onDialogResult(boolean canceled) {if (canceled) {runOnUiThread(new Runnable() {@Overridepublic void run() {// FIXME}}, 0);}}public void showPhoto(String path){cur = path;showPhoto(Imgcodecs.imread(getFile(code,path),0));}private void showPhoto(Mat mat){if(mat==null){runOnUiThread(() -> photoView.setBackgroundColor(Color.parseColor("#FF0000")));}else {try {Bitmap bitmap = Bitmap.createBitmap((int) mat.size().width, (int) mat.size().height, Bitmap.Config.ARGB_8888);Utils.matToBitmap(mat, bitmap);runOnUiThread(() -> photoView.setBackground(new BitmapDrawable(getResources(), bitmap)));//todo bitmap回收} catch (Exception e) {e.printStackTrace();}}}private void showFilePhoto(File file){try {FileInputStream fis = new FileInputStream(file);Bitmap bitmap = BitmapFactory.decodeStream(fis);//System.out.println("图片数据: "+bitmap.getByteCount());runOnUiThread(()->photoView.setBackground(new BitmapDrawable(getResources(),bitmap)));} catch (Exception e) {e.printStackTrace();}}private void deletePhoto(){File file = new File(getFile(code,cur));file.delete();faceArr.remove(cur);faceNameAdapter.notifyDataSetChanged();photoView.setBackgroundColor(Color.parseColor("#FF0000"));}private void takeFacePhoto(Mat mGray){Log.d(TAG, "takeFacePhoto: try");try {MatOfRect faces = new MatOfRect();if (classifier != null)classifier.detectMultiScale(mGray, faces, 1.1, 2, 2, new Size(mAbsoluteFaceSize, mAbsoluteFaceSize), new Size());Rect[] rectArr = faces.toArray();if(rectArr.length<=0) return;Rect rect = rectArr[0] ;double area = rect.area();for(int i=1;i<rectArr.length;i++){double a = rectArr[i].area();if(a>area){area = a;rect = rectArr[i];//选取最大的}}Log.d(TAG, "takeFacePhoto: do");Mat mat = new Mat(mGray,rect);String fileName = System.currentTimeMillis()+".png";Imgcodecs.imwrite(getFile(code,fileName),mat);isTakePhoto = false;showPhoto(mat);faceArr.add(fileName);cur = fileName;faceNameAdapter.notifyDataSetChanged();}catch (Exception e){e.printStackTrace();}}FaceNameAdapter faceNameAdapter;ArrayList<String> faceArr = new ArrayList<>();private void initFaceList(){File folder = new File(getFolder(code));for (String name : folder.list()) {if (name.endsWith(".png"))faceArr.add(name);}LinearLayoutManager llm = new LinearLayoutManager(this);llm.setOrientation(LinearLayoutManager.VERTICAL);RecyclerView rv = findViewById(R.id.face_rv);rv.setLayoutManager(llm);faceNameAdapter = new FaceNameAdapter(this,faceArr);rv.setAdapter(faceNameAdapter);}}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:id="@+id/container"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"android:background="#ffffff"tools:ignore="HardcodedText" ><RelativeLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"><TextView style="@style/style" android:layout_alignParentStart="true" android:id="@+id/face_code" android:hint="条码"/><TextView style="@style/style" android:layout_centerInParent="true" android:text="人脸录入" /><Button style="@style/button" android:layout_alignParentEnd="true" android:id="@+id/back" android:text="返回" /></RelativeLayout><TextViewandroid:text="请正对摄像头拍摄脸部"android:textColor="#FF0000"android:gravity="center"android:layout_width="match_parent"android:layout_height="wrap_content"/><LinearLayoutandroid:layout_marginTop="5dp"android:layout_width="match_parent"android:layout_height="wrap_content"android:gravity="center"android:orientation="horizontal"><SurfaceViewandroid:id="@+id/user_face_surface_view"android:layout_width="480dp"android:layout_height="400dp" /><LinearLayoutandroid:layout_marginStart="5dp"android:layout_marginEnd="5dp"android:layout_width="wrap_content"android:layout_height="wrap_content"android:orientation="vertical"><Buttonandroid:background="#aaaaaa"android:layout_gravity="center_horizontal"android:id="@+id/take_photo"android:layout_margin="5dp"android:layout_width="@dimen/button_size"android:layout_height="@dimen/button_size"android:contentDescription="@string/camera"android:text="拍"/><androidx.recyclerview.widget.RecyclerViewandroid:layout_width="wrap_content"android:layout_height="310dp"android:id="@+id/face_rv"/><Buttonandroid:background="#aaaaaa"android:layout_gravity="center_horizontal"android:id="@+id/delete_photo"android:layout_margin="5dp"android:layout_width="@dimen/button_size"android:layout_height="@dimen/button_size"android:contentDescription="@string/camera"android:text="删"/></LinearLayout><Viewandroid:id="@+id/face_photo"android:layout_width="400dp"android:layout_height="400dp"android:background="#FF0000" /></LinearLayout></LinearLayout>