Android之 Camera相机使用

news2024/11/15 20:59:16

一 简介

1.1 随着信息时代的发展,相机在我们生活中使用越来越频繁,也成为手机的基本配置之一。相机可以用来拍照,拍视频,人脸识别,视频聊天,扫码支付,监控等常见领域

不管什么场景,基本原理都差不多,都要先通过相机采集原始数据,也就是二进制字节数据,我们可以对原始数据做对应的操作,比如保存成图片,或者分析数据内容等等。

1.2 Android相机的API到目前发展了3个版本,如下面官方api文档所示

Camera
此类是用于控制设备相机的旧版 API,现已弃用,在Android5.0以下使用
Camera2
此软件包是用于控制设备相机的主要 API,Android5.0以上使用
CameraX
基于Camera 2 API封装,简化了开发流程,并增加生命周期控制

1.3 各版本优点

Camera

  • 检测设备摄像头,打开相机
  • 创建预览画面,显示实时预览画面
  • 设置相机参数,进行拍照监听
  • 监听中,保存图片资源或者直接操作原始数据
  • 释放相机资源

Camera2

  • 改进了新硬件的性能。Supported Hardware Level的概念,不同厂商对Camera2的支持程度不同,从低到高有LEGACY、LIMITED、FULL 和 LEVEL_3四个级别
  • 以更快的间隔拍摄图像
  • 显示来自多个摄像机的预览
  • 直接应用效果和滤镜

CameraX

  • CameraX 是一个 Jetpack 支持库,目的是简化Camera的开发工作,它是基于Camera2 API的基础,向后兼容至 Android 5.0(API 级别 21)。
  • 易用性,只需要几行代码就可以实现预览和拍照
  • 保持设备的一致性,在不同相机设备上,对宽高比、屏幕方向、旋转、预览大小和高分辨率图片大小,做到都可以正常使用
  • 相机特性的扩展,增加人像、HDR、夜间模式和美颜等功能

1.4 如果对相机要求不高,只是单纯的解析数据,那用Camera就可以了。虽然官方说已过时,但还是有非常多的场合使用。 

二 基于扫码支付的Camera的使用 

2.1 添加相机权限

<uses-feature
   android:name="android.hardware.camera"
   android:required="true" />
 
<uses-permission android:name="android.permission.CAMERA" />

2.2 检查相机权限

//权限请求
public final int REQUEST_CAMERA_PERMISSION = 1;
private String cameraPermission = Manifest.permission.CAMERA;

private void checkCameraPermission() {
	//检查是否有相机权限
	if (ContextCompat.checkSelfPermission(this, cameraPermission) != PackageManager.PERMISSION_GRANTED) {
		//没权限,请求权限
		ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA},
				REQUEST_CAMERA_PERMISSION);
	} else {
		//有权限
		createSurfaceView();
	}
}

//权限请求回调
@Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
	switch (requestCode) {
		case REQUEST_CAMERA_PERMISSION:
			if (grantResults != null && grantResults.length > 0
					&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
				//用户同意权限
				createSurfaceView();
			} else {
				// 权限被用户拒绝了,可以提示用户,关闭界面等等。
				Toast.makeText(this, "拒绝权限,请去设置里面手动开启权限", Toast.LENGTH_SHORT).show();
			}
			break;
	}
}

2.3 用SurfaceView来加载相机画面

/**
 * 创建预览画面
 */
private SurfaceView surfaceView;
private SurfaceHolder mHolder;

public void createSurfaceView() {
	//创建预览
	surfaceView = new SurfaceView(this);
	flContent.removeAllViews();
	flContent.addView(surfaceView);
	//获取预览的管理器
	mHolder = surfaceView.getHolder();
	//监听预览状态
	mHolder.addCallback(new SurfaceHolder.Callback() {
		@Override
		public void surfaceCreated(@NonNull SurfaceHolder surfaceHolder) {
			//预览控件创建成功的时候,打开相机并预览
			openCamera(Camera.CameraInfo.CAMERA_FACING_BACK);
		}

		@Override
		public void surfaceChanged(@NonNull SurfaceHolder surfaceHolder, int i, int i1, int i2) {

		}

		@Override
		public void surfaceDestroyed(@NonNull SurfaceHolder surfaceHolder) {
			//预览控件销毁的时候,释放相机资源
			releaseCamera();
		}
	});
}

2.4 打开相机

private Camera mCamera;
private Camera.CameraInfo cameraInfo;

/**
 * 打开相机
 * Camera.CameraInfo.CAMERA_FACING_FRONT前置
 * Camera.CameraInfo.CAMERA_FACING_BACK后置
 *
 * @param cameraIndex 摄像头的方位
 */
public void openCamera(int cameraIndex) {
	try {
		//先释放相机资源
		releaseCamera();
		//获取相机信息
		if (cameraInfo == null) {
			cameraInfo = new Camera.CameraInfo();
		}
		//获取相机个数
		int cameraCount = Camera.getNumberOfCameras();
		//由于不知道第几个是前置摄像头,遍历获取前置摄像头
		for (int camIdx = 0; camIdx < cameraCount; camIdx++) {
			Camera.getCameraInfo(camIdx, cameraInfo);

			if (cameraInfo.facing == cameraIndex) {
				mCamera = Camera.open(camIdx);
				break;
			}
		}

		//开启预览
		startPreview();
	} catch (Exception e) {
		//获取相机异常
		mCamera = null;
	}
}

2.5 开始预览

/**
 * 开始预览
 */
public void startPreview() {
	try {
		//获取屏幕宽高,预览尺寸默认为屏幕的屏幕
		WindowManager manager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
		Display display = manager.getDefaultDisplay();
		int shortSize = display.getWidth();
		int longSize = display.getHeight();
		//设置相机参数
		initPreviewParams(shortSize, longSize);
		//设置相机方向
		adjustCameraOrientation();
		//预览方式一,没缓冲区,会频繁GC
		//mCamera.setPreviewCallback(previewCallback);
		//绑定预览视图
		mCamera.setPreviewDisplay(mHolder);
		//设置缓冲区
		mCamera.addCallbackBuffer(new byte[shortSize * longSize * 3 / 2]);
		mCamera.setPreviewCallbackWithBuffer(previewCallback);
		//开始预览
		mCamera.startPreview();
	} catch (IOException e) {

	}
}

2.6 配置相机参数和预览大小

/**
 * 设置相机参数
 */
private void initPreviewParams(int shortSize, int longSize) {
	if (mCamera != null) {
		Camera.Parameters parameters = mCamera.getParameters();
		//获取手机支持的尺寸
		List<Camera.Size> sizes = parameters.getSupportedPreviewSizes();
		//获取合适的预览尺寸,保证不变形
		Camera.Size bestSize = getBestSize(shortSize, longSize, sizes);
		//设置预览大小
		parameters.setPreviewSize(bestSize.width, bestSize.height);
		//设置图片大小,拍照
		parameters.setPictureSize(bestSize.width, bestSize.height);
		//设置格式,所有的相机都支持 NV21格式
		parameters.setPreviewFormat(ImageFormat.NV21);
		//设置聚焦,如果拍照就设置持续对焦FOCUS_MODE_CONTINUOUS_PICTURE,其它可以自动对焦FOCUS_MODE_AUTO
		parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);

		mCamera.setParameters(parameters);
	}
}

/**
 * 获取预览最佳尺寸
 */
private Camera.Size getBestSize(int shortSize, int longSize, List<Camera.Size> sizes) {
	Camera.Size bestSize = null;
	float uiRatio = (float) longSize / shortSize;
	float minRatio = uiRatio;
	for (Camera.Size previewSize : sizes) {
		float cameraRatio = (float) previewSize.width / previewSize.height;

		//如果找不到比例相同的,找一个最近的,防止预览变形
		float offset = Math.abs(cameraRatio - minRatio);
		if (offset < minRatio) {
			minRatio = offset;
			bestSize = previewSize;
		}
		//比例相同
		if (uiRatio == cameraRatio) {
			bestSize = previewSize;
			break;
		}

	}
	return bestSize;
}

2.7 配置预览方向

/**
 * 调整预览方向
 * 由于手机的图片数据都来自摄像头硬件传感器,这个传感器默认的方向横向的,所以要根据前后摄像头调整方向
 */
private void adjustCameraOrientation() {
	//判断当前的横竖屏
	int rotation = getWindowManager().getDefaultDisplay().getRotation();

	int degress = 0;
	//获取手机的方向
	switch (rotation) {
		case Surface.ROTATION_0:
			degress = 0;
			break;
		case Surface.ROTATION_90:
			degress = 90;
			break;
		case Surface.ROTATION_180:
			degress = 180;
			break;
		case Surface.ROTATION_270:
			degress = 270;
			break;
	}
	int result = 0;
	if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_BACK) {
		//后置摄像头
		result = (cameraInfo.orientation - degress + 360) % 360;
	} else if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
		//前置摄像头,多一步镜像
		result = (cameraInfo.orientation + degress) % 360;
		result = (360 - result) % 360;
	}
	mCamera.setDisplayOrientation(result);
}

 2.8 监听预览数据

 /**
 * 预览数据监听
 */
Camera.PreviewCallback previewCallback = new Camera.PreviewCallback() {
	public void onPreviewFrame(byte[] data, Camera camera) {
		if (data != null) {
			//获取预览分辨率
			Camera.Parameters parameters = camera.getParameters();
			Camera.Size size = parameters.getPreviewSize();
			//拿到字节数组,可以生成图片,也可以解析数据(比如二维码扫描,人脸识别)
			//................................

//                //创建解码图像,并转换为原始灰度数据,注意图片是被旋转了90度的
//                Image source = new Image(size.width, size.height, "Y800");
//                //图片旋转了90度,将扫描框的TOP作为left裁剪
//                source.setData(data);//填充数据
//                //解码,返回值为0代表失败,>0表示成功
//                int dataResult = mImageScanner.scanImage(source);
		}

		//不管有没有数据,重新设置缓冲区,避免频繁GC
		camera.addCallbackBuffer(data);
	}
};

2.9  拍照并保存照片

/**
 * 拍照
 */
public void takePicture() {
	if (mCamera == null) {
		Toast.makeText(this, "请打开相机", Toast.LENGTH_SHORT).show();
		return;
	}
	mCamera.takePicture(new Camera.ShutterCallback() {
		@Override
		public void onShutter() {

		}
	}, null, new Camera.PictureCallback() {
		@Override
		public void onPictureTaken(byte[] data, Camera camera) {
			new SavePicAsyncTask(CameraActivity.this, cameraInfo.facing, data).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
		}
	});
}

/**
 * 保存图片
 */
class SavePicAsyncTask extends AsyncTask<Void, Void, File> {
	Context context;
	int facing;
	byte[] data;

	public SavePicAsyncTask(Context context, int facing, byte[] data) {
		this.context = context;
		this.facing = facing;
		this.data = data;
	}


	@Override
	protected File doInBackground(Void... voids) {
		//保存的文件
		File picFile = null;
		try {
			Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
			if (bitmap == null) {
				return null;
			}
			//保存之前先调整方向
			if (facing == Camera.CameraInfo.CAMERA_FACING_BACK) {
				Matrix matrix = new Matrix();
				matrix.postRotate(90);
				bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
			} else {
				Matrix matrix = new Matrix();
				matrix.postRotate(270);
				bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
			}

			// SD卡根目录
			File dir = context.getExternalFilesDir("print");
			if (!dir.exists()) {
				dir.mkdirs();
			}
			picFile = new File(dir, System.currentTimeMillis() + ".jpg");
			FileOutputStream fos = new FileOutputStream(picFile);
			bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos);
			fos.close();
			bitmap.recycle();
			return picFile;

		} catch (Exception e) {
			e.printStackTrace();
		}
		return picFile;
	}

	@Override
	protected void onPostExecute(File file) {
		super.onPostExecute(file);
		if (file != null) {
			Toast.makeText(context, "图片保存成功", Toast.LENGTH_SHORT).show();
		} else {
			Toast.makeText(context, "图片保存失败", Toast.LENGTH_SHORT).show();
		}
	}
}

2.10 释放相机资源

/**
 * 释放相机资源
 */
private void releaseCamera() {
	if (mCamera != null) {
		mCamera.stopPreview();
		mCamera.stopFaceDetection();
		mCamera.setPreviewCallback(null);
		mCamera.release();
		mCamera = null;
	}
}

三 重点注意

3.1 对焦模式setFocusMode

FOCUS_MODE_AUTO:自动对焦
FOCUS_MODE_INFINITY:无穷远
FOCUS_MODE_MACRO:微距
FOCUS_MODE_FIXED:固定焦距
FOCUS_MODE_EDOF:景深扩展
FOCUS_MODE_CONTINUOUS_PICTURE:持续对焦(针对照片)
FOCUS_MODE_CONTINUOUS_VIDEO:(针对视频)

3.2  预览格式setPreviewFormat,默认返回NV21的数据

ImageFormat.NV16
ImageFormat.NV21
ImageFormat.YUY2
ImageFormat.YV12
ImgaeFormat.RGB_565
ImageFormat.JPEG

3.3  设置预览尺寸,setPreviewSize。根据屏幕尺寸设备最佳预览尺寸

/**
 * 获取预览最佳尺寸
 */
private Camera.Size getBestSize(int shortSize, int longSize, List<Camera.Size> sizes) {
    Camera.Size bestSize = null;
    float uiRatio = (float) longSize / shortSize;
    float minRatio = uiRatio;
    for (Camera.Size previewSize : sizes) {
        float cameraRatio = (float) previewSize.width / previewSize.height;

        //如果找不到比例相同的,找一个最近的,防止预览变形
        float offset = Math.abs(cameraRatio - minRatio);
        if (offset < minRatio) {
            minRatio = offset;
            bestSize = previewSize;
        }
        //比例相同
        if (uiRatio == cameraRatio) {
            bestSize = previewSize;
            break;
        }

    }
    return bestSize;
}

3.4  预览方向,上面说了,传感器方向默认横向的,所以预览成像要调整方向。

下图是传感器方向和需要调整的角度图示

 3.5 拍照图片方向,由于拍照也是存储的传感器方向,所以也需要做旋转

保存之前先调整方向
if (facing == Camera.CameraInfo.CAMERA_FACING_BACK) {
     bitmap = BitmapUtils.rotate(bitmap, 90);
} else {
     bitmap = BitmapUtils.rotate(bitmap, 270);

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/465376.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

DolphinScheduler 2.0.5详解

文章目录 第一章 DolphinScheduler介绍1.1 关于DolphinScheduler1.2 DolphinScheduler特性1.3 配置建议1.3.1 Linux 操作系统版本要求1.3.2 服务器建议配置1.3.3 生产环境1.3.4 网络要求1.3.5 客户端 Web 浏览器要求 第二章 DolphinScheduler安装部署2.1 安装部署介绍2.2 单机版…

Mybatis笔记分享【狂神说java】

MyBatis 1、简介 1.1什么是MyBatis MyBatis 是一款优秀的持久层框架 它支持自定义 SQL、存储过程以及高级映射 MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作 MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO&#xff08;…

Windows环境下使用Internet Information Service( IIS)管理器上搭建Web资源网站

一、业务需求 在项目的开发过程中,需要将指定文件夹发布称为一个网站,可以通过网页查看该文件夹下的内容和子目录(及其子目录内容);同时也可以将内容上传到该文件夹中,方便他人使用,如下图所示: 二、思路分析 将文件夹发布为网站,可以使用Windows的IIS管理器搭建一个W…

Scala之模式匹配与隐式转换

目录 模式匹配&#xff1a; 基础语法如下&#xff1a; 模式守卫&#xff1a; 类型匹配&#xff1a; 对象匹配 样例类&#xff1a; 偏函数&#xff1a; 偏函数的化简写法&#xff1a; 偏函数的使用&#xff1a; 隐式转换&#xff1a; 官方定义&#xff1a; 个人理解&…

(小甲鱼python)文件永久存储(下)总结 文件处理with语句和上下文管理器、pickle模块详解

一、基础复习 上节课回顾&#xff1a; 1.文件永久存储(上) python文件永久存储(创建打开文件、文件对象的各种方法及含义) 2.文件永久存储(中)总结 路径处理 pathlib–面向对象的文件系统路径 绝对路径vs相对路径&#xff08;路径查询、路径修改、查找功能&#xff09; 二、文…

【JavaScript】new命令精华总结

相关概念 对象是什么? 1.对象是单个实物的抽象 2.对象是一个容器&#xff0c;封装了属性和方法 属性是对象的状态&#xff0c;方法是对象的行为&#xff0c;把对象中的函数一般称为方法 构造函数 专门用来生成实例对象的函数&#xff0c;是对象的模板&#xff0c;第一个字…

vue2数据响应式原理(7) 收集依赖,用get和set叙述出最基础的至高vue哲学

收集依赖在整个数据响应式中算是比较难的 首先 要理解这里所指的依赖 依赖 可能vue项目做多了就会想到 npm i 但其实跟这个是没有什么关系的 我们这里所指的依赖 是用到数据的地方 什么地方用到数据 什么地方就是依赖 简单说 就是依赖这个响应式数据 首先 我们看一下 vue1 和…

【Linux初阶】进程状态 | Linux下常见进程状态讲解 进程循环打印方法 ls>makefile指令 makefile$@^特殊符号的应用

&#x1f31f;hello&#xff0c;各位读者大大们你们好呀&#x1f31f; &#x1f36d;&#x1f36d;系列专栏&#xff1a;【Linux初阶】 ✒️✒️本篇内容&#xff1a;进程状态的概念&#xff0c;进程状态在普遍操作系统层面和Linux层面的理解&#xff0c;Linux常见进程状态&…

【论文阅读】轻量化网络MobileNet-V1

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、摘要二、MobileNet-V1核心点介绍&#xff1a;普通卷积和深度可分离卷积三、两个超参数四。后续实验 前言 今天重温一下轻量化经典论文MobileNet-V1&#x…

拿下车载激光雷达份额「第一」,图达通寻求「变阵」

在2022年的量产元年之后&#xff0c;激光雷达正尝试进入真正大规模量产周期。在此之前&#xff0c;有一些关键问题需要解决&#xff0c;其中包括&#xff1a;其一&#xff0c;帮助主机厂将激光雷达真正用起来&#xff0c;发挥价值&#xff1b;其二&#xff0c;丰富产品品类&…

【C语言】文件的相关操作(一文10分钟彻底弄懂)

前言&#xff1a; 欢迎各位童学来到本文&#xff0c;本文将主要通过一个实战案例&#xff08;奥运会奖牌&#xff09;来帮助各位小伙伴们熟悉并掌握文件的相关操作&#xff0c;相信经过一个实战案例的小项目后大家对文件的相关操作应该都能够轻车熟路了&#xff01; &#x1f…

仔细观察Binder和mmap;分析Android进程间通信

前言 Binder是Android系统中的一种IPC&#xff08;进程间通信&#xff09;机制&#xff0c;它使得不同进程中的组件能够互相交互和通信。在Binder中&#xff0c;一个进程中的客户端和另一个进程中的服务器之间通常通过Binder驱动程序进行通信。这种通信方式能够提供安全性和效…

材料写作技巧:关于“新”排比句40例

1.是新时代新征程举旗定向的“宣言书”&#xff0c;是新时代新征程伟大思想的“领航标”&#xff0c;是新时代新征程推进中国式现代化的“动员令”&#xff0c;是新时代新征程自我革命的“冲锋号”。 2.胸怀家国&#xff0c;树立远大理想&#xff0c;奋力担当新时代青年使命&a…

真题详解(数据流图平衡)-软件设计(五十九)

真题详解&#xff08;磁盘&#xff09;-软件设计&#xff08;五十八)https://blog.csdn.net/ke1ying/article/details/130376289 如何保证数据流图平衡&#xff1f; 父图中输入流输出流的名称和数目必须和子图的相同。 父图中一条输入输出流可以对应子图几条输入输出流&…

MATLAB实现OCR自动阅卷,识别答题卡进行成绩统计

利用MATLAB进行答题卡识别编程设计&#xff0c;最主要的是实现了将答题卡中被填涂的答案提取出来&#xff0c;然后与标准的答案进行比对。通过相关的算法&#xff0c;算出考生填涂正确的题数&#xff0c;并统计计算后的得分。 每种答题卡都有很明显的助识别标记&#xff0c;像…

LeetCode - 494 目标和

目录 题目来源 题目描述 示例 提示 题目解析 算法源码 题目来源 494. 目标和 - 力扣&#xff08;LeetCode&#xff09; 题目描述 给你一个整数数组 nums 和一个整数 target 。 向数组中的每个整数前添加 或 - &#xff0c;然后串联起所有整数&#xff0c;可以构造一个…

MathType如何成功插入到word

有时候我们重装mathtype的时候&#xff0c;我们的word里嵌入的mathtype没有了&#xff0c;因此我们如何让它重新出来呢&#xff1f;下面我们来看看。 1、我们打开word&#xff0c;点击“选项”&#xff0c;点击“加载项”找到如图所示的路径内容&#xff08;根据自己电脑的实际…

IDA 知识汇总

工具使用-IDA从入门到理解 - 简书作者ID:leishi-yanmu IDA对于各位师傅应该无需简介了,如果写的不对的地方,还望师傅们多多包涵。讲解的时候会涉及到笔者在学习和使用时候的理解。 启动界面介绍: ...https://www.jianshu.com/p/190805574432[原创]【iOS逆向与安全】利用IDA…

实现网页顶部线性加载进度条

插件一&#xff1a;NProgress.js 下载链接&#xff1a;https://github.com/rstacruz/nprogress 插件二&#xff1a;MProgress.js 下载链接&#xff1a;https://github.com/lightningtgc/mprogress.js/ 这两个插件都是实现网页加载进度条&#xff0c;并且默认方法有四个&…

kitti数据集预处理

kitti数据集预处理 0.引言0.1.calib0.2.oxts(gps/imu)0.3.velodyne0.4.image_2/30.5.kitti-step/panoptics0.6.label 1.create_kitti_depth_maps2.create_kitti_masks3.create_kitti_metadata4.extract_dino_features5.run_pca 0.引言 官网参考链接1参考链接2 注&#xff1a;…