基于Android设备获取USB外接摄像头的图像

背景

  本人在读研期间接到的项目,需要用一个工业内窥镜(支持USBType-C接口)外接到 Android 设备上,获取其中图像进行目标检测等后续需求。本文主要讲Android设备如何显示、获取USB摄像头采集到的每一帧图像,后续可以通过深度学习或者Android版本的OpenCV进行目标检测。
   查阅目前的技术博客,得到了几位大牛的文章指点,最终成功完成了项目。所以本文期望在前辈的基础上进行总结,给后来者提供一些帮助。
  因为行文时作者还是在校学生,接触Android时间也不算长,可能用方法比较繁琐笨拙,如有不对处,望指正。

开发环境

硬件

荣耀V10手机一台(鸿蒙OS)、Redmi K30 PRO(Android 11)、工业内窥镜(支持USBType-C接口)

软件

Android studio 4.2.1

简介

UVC全称为USB Video Class,直接翻译过来的意思就是:USB视频类,它是一种专门为USB视频捕获设备定义的协议标准。
这个标准是Microsoft与另外几家设备厂商联合推出的为USB视频捕获设备定义的协议标准,已经成为USB org标准之一。
现在的主流操作系统,都已提供UVC设备驱动,因此符合UVC规格的硬件设备在不需要安装任何的驱动程序下即可在主机中正常使用。是的,目前Android系统已经支持uvc设备。(摘自小驰笔记:https://www.jianshu.com/p/972e05fa76a3

本文实现的功能使参考三位大牛的文章或源码。

  • UVCCamera 开源项目
    里面有8个例程(从易到难),需要引用作者自己的 libuvccamera 库,有参考价值。(https://github.com/saki4510t/UVCCamera

  • 博主 小驰笔记 的开源项目
    简书博主“小驰嘻嘻”的项目,小驰博主的文章要好好读一下,里面也是用到上面的 libuvccamera 库,但是里面用到了AIDL,像我这样的新手看不懂。(捂脸)(https://github.com/yorkZJC/UvcCameraDemo

  • 博主 jiangdongguo 的开源项目
    这里面需要引入博主自己的 libuvccamera 库,不能用 UVCCamera 原作者的库,照着他的 demo 编写程序,是可以在自己的项目中实现USB摄像头预览的。(GitHub源码地址:https://github.com/jiangdongguo/AndroidUSBCamera

最终本文参考了博主 jiangdongguo 的开源项目,并在博主无名之辈FTER文章的指导下完成。(https://blog.csdn.net/andrexpert/article/details/78324181

那我们开始吧

导入项目

下载博主 jiangdongguo 的开源项目,在自己的项目中引入该项目的 libuvccamera 模块。
在这里插入图片描述
![在这里插入图片描述](https://img-blog.csdnimg.cn/72f3ce94ccf0475db1216346569cbf73.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWUguS29uZw==,size_20,color_FFFFFF,t_70,g_se,x_1
接下来会遇到一个问题
在这里插入图片描述
参考博客 https://blog.csdn.net/shenggaofei/article/details/98055433 在project目录下的build.gradle对应位置添加以下代码解决(版本号比较低,所以为黄色,不影响运行)

classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5'

在这里插入图片描述
下一个问题
在这里插入图片描述
原因是 libuvccamera 模块中的各个版本已经定义为变量,而我们引入的时候没有给这些变量赋值,于是在开源项目中,找到声明版本号的地方,复制到自己项目的对应位置(也可以自己定义),之后重新同步一下,所有报错就消失了。
在这里插入图片描述
最后一步
在自己项目的 build.gradle 中添加依赖,否则 UVCCameraTextureView 图像预览控件将无法使用。

implementation project(':libusbcamera')

在这里插入图片描述

布局、权限与初始化

布局根据需求来吧,本文项目既要实时预览外接 USB相机拍摄的图像,又需要对图像进行分析,故首先把预览图像的控件摆上。 libuvccamera 模块中有一个定义好的控件 UVCCameraTextureView 直接拿来用就行。本文demo只有一个MainActivity,布局如下:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <com.serenegiant.usb.widget.UVCCameraTextureView
            android:id="@+id/camera_view"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_gravity="center" />

    </FrameLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

权限方面,也按照开源项目配置好即可,在 AndroidManifest.xml 中加入以下权限:

	<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
        android:maxSdkVersion="30"
        tools:ignore="ScopedStorage"
        />
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.RECORD_AUDIO" />

    <uses-feature android:name="android.hardware.usb.host" />
    <uses-feature android:name="android.hardware.camera" />
    <uses-feature android:name="android.hardware.camera.autofocus" />

如果报红,就按 alt+Enter 解决
初始化方面,在之前下载的 jiangdongguo 的开源项目 里面找到一个叫 CrashHandler.java 的文件,拷贝到自己的项目中,会出现两处报错。

第12行报错,源于原作者引用自己编写的类所在文件夹的名字,由于类没有拷贝过来而报错,删除这行即可。
136行就是该类不存在而报错,可以改为自己的类的名字,也可以干脆删除MyApplication.DIRECTORY_NAME,让日志文件直接存放在根目录下,日志文件会存放调试时候出现的bug。

还有一个地方需要注意,由于 Android 10 以上系统不支持静态的写权限,所以需要把 build.gradle 中 compileSdkVersion 和 targetSdkVersion 均改到 28 及以下。(这个问题不会在编译期报错,只会在插入USB摄像头的时候弹出错误提示,并且无法正常退出程序。困扰了我好久,看来还是对Android了解得不够多…)

MainActivity.java

package com.example.myapplication;

import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.ContextCompat;

import android.Manifest;
import android.content.pm.PackageManager;
import android.graphics.PixelFormat;
import android.hardware.usb.UsbDevice;
import android.os.Build;
import android.os.Bundle;
import android.os.Looper;
import android.os.StrictMode;
import android.view.Surface;
import android.view.View;
import android.widget.Toast;

import com.jiangdg.usbcamera.UVCCameraHelper;
import com.jiangdg.usbcamera.utils.FileUtils;
import com.serenegiant.usb.CameraDialog;
import com.serenegiant.usb.USBMonitor;
import com.serenegiant.usb.UVCCamera;
import com.serenegiant.usb.common.AbstractUVCCameraHandler;
import com.serenegiant.usb.widget.CameraViewInterface;

public class MainActivity extends AppCompatActivity implements CameraDialog.CameraDialogParent, CameraViewInterface.Callback {
    private CrashHandler mCrashHandler;
    public View mTextureView;
    private UVCCameraHelper mCameraHelper;
    private CameraViewInterface mUVCCameraView;
    private boolean isRequest;
    private boolean isPreview;

    private UVCCameraHelper.OnMyDevConnectListener listener = new UVCCameraHelper.OnMyDevConnectListener() {
        @Override
        public void onAttachDev(UsbDevice device) {
            // request open permission
            if (!isRequest) {
                isRequest = true;
                if (mCameraHelper != null) {
                    mCameraHelper.requestPermission(0);
                }
            }
        }

        @Override
        public void onDettachDev(UsbDevice device) {
            // close camera
            if (isRequest) {
                isRequest = false;
                mCameraHelper.closeCamera();
                showShortMsg(device.getDeviceName() + " is out");
            }
        }

        @Override
        public void onConnectDev(UsbDevice device, boolean isConnected) {
            if (!isConnected) {
                showShortMsg("fail to connect,please check resolution params");
                isPreview = false;
            } else {
                isPreview = true;
                showShortMsg("connecting");
                // initialize seekbar
                // need to wait UVCCamera initialize over
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            Thread.sleep(30);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        Looper.prepare();
                        Looper.loop();
                    }
                }).start();
            }
        }

        @Override
        public void onDisConnectDev(UsbDevice device) {
            showShortMsg("disconnecting");
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        AccessRequest();

        /**------- USBCamera引入 ---------*/
        mCrashHandler = CrashHandler.getInstance();
        mCrashHandler.init(getApplicationContext(), getClass());

        mTextureView = findViewById(R.id.camera_view);

        // step.1 initialize UVCCameraHelper
        mUVCCameraView = (CameraViewInterface) mTextureView;
        mUVCCameraView.setCallback(this);
        mCameraHelper = UVCCameraHelper.getInstance();
        mCameraHelper.setDefaultFrameFormat(UVCCameraHelper.FRAME_FORMAT_MJPEG);
        mCameraHelper.initUSBMonitor(this, mUVCCameraView, listener);

        mCameraHelper.setOnPreviewFrameListener(new AbstractUVCCameraHandler.OnPreViewResultListener() {
            @Override
            public void onPreviewResult(byte[] data) {
                // 获取单帧图像回调
            }
        });
    }

    public boolean isCameraOpened() {
        return mCameraHelper.isCameraOpened();
    }

    private void showShortMsg(String msg) {
        Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onSurfaceCreated(CameraViewInterface view, Surface surface) {
        if (!isPreview && mCameraHelper.isCameraOpened()) {
            mCameraHelper.startPreview(mUVCCameraView);
            isPreview = true;
        }
    }

    @Override
    public void onSurfaceChanged(CameraViewInterface view, Surface surface, int width, int height) {

    }

    @Override
    public void onSurfaceDestroy(CameraViewInterface view, Surface surface) {
        if (isPreview && mCameraHelper.isCameraOpened()) {
            mCameraHelper.stopPreview();
            isPreview = false;
        }
    }

    @Override
    public USBMonitor getUSBMonitor() {
        return mCameraHelper.getUSBMonitor();
    }

    @Override
    public void onDialogResult(boolean canceled) {
        if (canceled) {
            showShortMsg("取消操作");
        }
    }

    @Override
    protected void onStart() {
        super.onStart();
        // step.2 register USB event broadcast
        if (mCameraHelper != null) {
            mCameraHelper.registerUSB();
        }
    }

    @Override
    protected void onResume() {
        super.onResume();

    }

    @Override
    protected void onStop() {
        super.onStop();
        // step.3 unregister USB event broadcast
        if (mCameraHelper != null) {
            mCameraHelper.unregisterUSB();
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        FileUtils.releaseFile();
        // step.4 release uvc camera resources
        if (mCameraHelper != null) {
            mCameraHelper.release();
        }
    }

    /**
     * 检查权限 方法
     */
    private boolean checkPermission() {
        //是否有权限
        boolean haveCameraPermission = ContextCompat.checkSelfPermission(this,
                Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED;
        boolean haveWritePermission = ContextCompat.checkSelfPermission(this,
                Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED;
        boolean haverReadPermission = ContextCompat.checkSelfPermission(this,
                Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED;
        return haveCameraPermission && haveWritePermission && haverReadPermission;
    }

    /**
     * 请求权限 方法
     */
    @RequiresApi(api = Build.VERSION_CODES.M)
    private void requestPermissions() {
        requestPermissions(new String[]{Manifest.permission.CAMERA, Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS, Manifest.permission.SYSTEM_ALERT_WINDOW,
                Manifest.permission.WRITE_EXTERNAL_STORAGE,
                Manifest.permission.READ_EXTERNAL_STORAGE}, 1);
    }

    private void AccessRequest() {
        //动态权限检测和申请
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {//大于Android 6.0
            if (!checkPermission()) { //没有或没有全部授权
                requestPermissions(); //请求权限
            }
        }

        //加 StrictMode, Android 7.0以后,获取文件Uri需要加上这么一段
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
            StrictMode.setVmPolicy(builder.build());
        }
    }

}

运行时遇到的问题

编译时又出现了问题
在这里插入图片描述
貌似找不到一个名叫 libusbcommon_v4.1.1 的文件。
这个文件在引入项目的时候其实已经就放在libuvccamera 模块中,把整个libs文件夹拷贝到自己的项目文件中,如下图所示。
在这里插入图片描述
然后在 build.gradle 中引入这个文件夹在这里插入图片描述

运行结果

在这里插入图片描述

其他

在这里可以调整摄像头的分辨率等设置。
在这里插入图片描述

  • 12
    点赞
  • 72
    收藏
    觉得还不错? 一键收藏
  • 8
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值