Android 使用Camera1实现相机预览、拍照、录像

2024-01-06 22:20

本文主要是介绍Android 使用Camera1实现相机预览、拍照、录像,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1. 前言

本文介绍如何从零开始,在Android中实现Camera1的接入,并在文末提供Camera1Manager工具类,可以用于快速接入Camera1
Android Camera1 API虽然已经被Google废弃,但有些场景下不得不使用。
并且Camera1返回的帧数据是NV21,不像Camera2CameraX那样,需要自己再转一层,才能得到NV21
Camera1的API调用也比Camera2简单不少,和CameraX的简单程度差不多,所以在一定的场景下,Camera1还是有其用途的。

2. 前置操作

2.1 添加权限

AndroidManifest中添加如下权限

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />

2.2 申请权限

别忘了申请权限

ActivityCompat.requestPermissions(this@WelComeActivity,arrayOf(android.Manifest.permission.WRITE_EXTERNAL_STORAGE,android.Manifest.permission.RECORD_AUDIO,android.Manifest.permission.CAMERA),123
)

2.3 声明XML布局

新建一个Activity,在其XML中声明SurfaceView

<?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"android:background="@color/black"android:layout_width="match_parent"android:layout_height="match_parent"><SurfaceViewandroid:id="@+id/surfaceView"android:layout_width="0dp"android:layout_height="0dp"app:layout_constraintDimensionRatio="9:16"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintRight_toRightOf="parent"app:layout_constraintTop_toTopOf="parent" /></androidx.constraintlayout.widget.ConstraintLayout>

3. 实现预览功能

3.1 添加SurfaceView的回调

binding.surfaceView.holder.addCallback(surfaceCallback)private var surfaceCallback: SurfaceHolder.Callback = object : SurfaceHolder.Callback {// Surface创建时override fun surfaceCreated(holder: SurfaceHolder) {}// Surface改变时override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {}// Surface销毁时override fun surfaceDestroyed(holder: SurfaceHolder) {}
}

3.2 打开相机

Surface创建时,也就是在surfaceCreated的时候,打开相机

private var camera: Camera? = null
private fun openCamera(holder: SurfaceHolder) {try {camera = Camera.open(cameraId)} catch (e: Exception) {e.printStackTrace()}
}

3.3 开始预览

当我们打开相机后,就可以开始预览了
这里首先将设置camera1预览的尺寸,一般来说,通过camera!!.parameters.supportedPreviewSizes获取到的列表中,第一项就是最推荐的尺寸了。

private fun setPreviewSize() {//获取摄像头支持的宽、高val supportedPreviewSizes: List<Camera.Size> = camera!!.parameters.supportedPreviewSizessupportedPreviewSizes.forEach {Log.i("ZZZZ", "${it.width}*${it.height}")}val parameters = camera?.parametersval size = supportedPreviewSizes[0]parameters?.setPreviewSize(size.width, size.height)camera?.setParameters(parameters)
}

接着,将SurfaceHolder设置到camera中。setPreviewDisplay接受一个SurfaceHolder对象作为参数,该对象表示预览显示的表面。通过调用setPreviewDisplay方法,可以将相机的预览数据输出到指定的表面对象上,从而在预览界面中显示出相机的拍摄画面。

camera?.setPreviewDisplay(holder)

接着调用setDisplayOrientation方法来设置相机的预览方向。该方法接受一个参数,即预览方向的度数。例如,如果要在竖直模式下使用相机,而默认的预览方向是水平的,那么就可以通过调用setDisplayOrientation方法将预览方向顺时针旋转90度。

camera?.setDisplayOrientation(90)

最后,调用startPreview()就可以启动相机的预览了

camera?.startPreview()

来看一下完整代码

private fun startPreview(holder: SurfaceHolder) {try {setPreviewSize()camera?.setPreviewDisplay(holder)camera?.setDisplayOrientation(90)camera?.startPreview()} catch (e: IOException) {e.printStackTrace()}
}

3.4 效果如下

4. 实现拍照功能

4.1 调用拍照接口

要进行拍照,调用camera.takePicture即可,它共有3个回调参数

  • ShutterCallback shutter(捕获图片瞬间的回调):快门回调是在拍照时快门按下的瞬间调用的回调。它允许您在拍照时执行一些自定义操作,例如触发闪光灯或显示自定义的拍照界面。
  • PictureCallback raw(原始图像数据回调):原始图像数据回调是在拍照后,获取到原始未压缩的数据时调用的回调。您可以在这个回调中对图像数据进行处理或保存。
  • PictureCallback jpeg(JPEG图像数据回调):JPEG图像数据回调是在拍照后,获取到图像的JPEG格式数据时调用的回调。您可以在这个回调中对JPEG图像数据进行处理或保存。

这里我们只需要用到jpeg回调

private val threadPool = Executors.newCachedThreadPool()binding.btnTakePicture.setOnClickListener {camera?.takePicture(null,null,{ data, camera ->//jpeg回调})
}

4.2 在jpeg回调中保存图片

//MediaFileUtils类详见本文附录
val pictureFile: File = MediaFileUtils.getOutputMediaFile(MEDIA_TYPE_IMAGE)!!
try {val fos = FileOutputStream(pictureFile)fos.write(data)fos.close()
} catch (e: FileNotFoundException) {Log.d(TAG, "File not found: ${e.message}")errorCallBack.invoke(e)
} catch (e: IOException) {Log.d(TAG, "Error accessing file: ${e.message}")errorCallBack.invoke(e)
}

来查看下效果,可以看到图片已经被保存了,但是图片的方向目前是有问题的。
在这里插入图片描述

4.3 解决图片保存的方向问题

所以,我们需要先将图片转成bitmap,旋转角度后,再保存
修改代码为如下代码

//路径示例 : /storage/emulated/0/Pictures/MyCameraApp/IMG_20230726_135652.jpg
val pictureFile: File = MediaFileUtils.getOutputMediaFile(MediaFileUtils.MEDIA_TYPE_IMAGE)!!
val bitmap = BitmapFactory.decodeByteArray(data, 0, data.size)
val matrix = Matrix()
matrix.postRotate(270F)
val rotatedBitmap: Bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true
)
//ImageUtils需要依赖 implementation 'com.blankj:utilcodex:1.31.1'
ImageUtils.save(rotatedBitmap, pictureFile, Bitmap.CompressFormat.JPEG)

来看一下效果,可以看到现在图片方向是对了,但是图片左右的内容是相反的
在这里插入图片描述

4.4 解决图片保存镜像问题

要解决图片的镜像问题,就调用一下matrix.postScale左右水平变换就好了

matrix.postScale(-1F, 1F, bitmap.width / 2F, bitmap.height / 2F)

完整代码如下

val pictureFile: File =
MediaFileUtils.getOutputMediaFile(MediaFileUtils.MEDIA_TYPE_IMAGE)!!
//路径示例 : /storage/emulated/0/Pictures/MyCameraApp/IMG_20230726_135652.jpg
val bitmap = BitmapFactory.decodeByteArray(data, 0, data.size)
val matrix = Matrix()
matrix.postRotate(270F)
matrix.postScale(-1F, 1F, bitmap.width / 2F, bitmap.height / 2F)
val rotatedBitmap: Bitmap = Bitmap.createBitmap(
bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true
)
//ImageUtils需要依赖 implementation 'com.blankj:utilcodex:1.31.1'
ImageUtils.save(rotatedBitmap, pictureFile, Bitmap.CompressFormat.JPEG)

5. 实现录像功能

要录制视频,需要使用MediaRecorder,若要使用 Camera1 拍摄视频,需要谨慎管理 CameraMediaRecorder,并且必须按特定顺序调用相应方法。您必须遵循以下顺序,才能使您的应用正常工作:

  • 打开相机。
  • 做好准备,并开始预览(如果您的应用会显示正在录制的视频,而通常情况下都是如此)。
  • 通过调用 Camera.unlock() 解锁相机,以供 MediaRecorder 使用。
  • 通过在 MediaRecorder 上调用以下方法来配置录制:
    • 通过 setCamera(camera) 关联您的 Camera 实例。
    • 调用 setAudioSource(MediaRecorder.AudioSource.CAMCORDER)
    • 调用 setVideoSource(MediaRecorder.VideoSource.CAMERA)
    • 调用 setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_1080P)) 以设置质量。
    • 调用 setOutputFile(getOutputMediaFile(MEDIA_TYPE_VIDEO).toString())
    • 如果您的应用提供视频预览,请调用 setPreviewDisplay(preview?.holder?.surface)
    • 调用 setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
    • 调用 setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT)
    • 调用 setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT)
    • 调用 prepare() 以完成 MediaRecorder 配置。
  • 如需开始录制,请调用 MediaRecorder.start()
  • 如需停止录制,请按以下顺序调用以下方法:
    • 调用 MediaRecorder.stop()
    • (可选)通过调用 MediaRecorder.reset() 移除当前的 MediaRecorder 配置。
    • 调用 MediaRecorder.release()
    • 通过调用 Camera.lock() 锁定相机,以便将来的 MediaRecorder 会话可以使用它。
  • 如需停止预览,请调用 Camera.stopPreview()
  • 最后,如需释放 Camera 以供其他进程使用,请调用 Camera.release()

具体可以见 Camera1 录制视频

下面直接附上代码,直接如下代码就好了

5.1 开始录制

fun startVideo(holder: SurfaceHolder) {mediaRecorder = MediaRecorder()//解锁相机,以供 MediaRecorder 使用camera?.unlock()//设置要用于视频捕获的相机mediaRecorder.setCamera(camera)//设置音频源mediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER)//设置视频源mediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA)//设置视频的输出格式和编码mediaRecorder.setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_1080P))//设置输出视频播放的方向mediaRecorder.setOrientationHint(270)//设置输出文件mediaRecorder.setOutputFile(getVideoFilePath(this))//指定 SurfaceView 预览布局元素mediaRecorder.setPreviewDisplay(holder.surface)try {mediaRecorder.prepare()} catch (e: IOException) {e.printStackTrace()releaseMediaRecorder()}Handler().postDelayed({try {mediaRecorder.start()} catch (e: IOException) {e.printStackTrace()releaseMediaRecorder()}}, 10)
}fun getVideoFilePath(context: Context?): String {val filename = "VIDEO_${System.currentTimeMillis()}.mp4"val dir = context?.getExternalFilesDir("video")return "${dir!!.path}/$filename"
}

5.2 停止播放

fun stopVideo() {mediaRecorder.stop()mediaRecorder.release()camera?.lock()
}

5.3 释放资源

fun releaseMediaRecorder() {if (mediaRecorder != null) {mediaRecorder.reset() // 清除配置mediaRecorder.release()//mediaRecorder = nullcamera?.lock()}
}

6. CameraHelper工具类

可以直接使用这个工具类,来快速接入Camera1

class CameraHelper(private val activity: AppCompatActivity,private var cameraId: Int,private var width: Int = 720,private var height: Int = 1280,
) : Camera.PreviewCallback {private var surfaceHolder: SurfaceHolder? = nullprivate var surfaceTexture: SurfaceTexture? = nullprivate var mCamera: Camera? = nullprivate var buffer: ByteArray? = nullprivate var bytes: ByteArray? = null/*** 打开相机** @param cameraId 后摄 Camera.CameraInfo.CAMERA_FACING_BACK*                 前摄 Camera.CameraInfo.CAMERA_FACING_FRONT*/private fun open(cameraId: Int) {//获得camera对象mCamera = Camera.open(cameraId)mCamera?.let { camera ->//配置camera的属性val parameters = camera.parameters//设置预览数据格式为nv21parameters.previewFormat = ImageFormat.NV21//这是摄像头宽、高setPreviewSize(parameters!!)// 设置摄像头 图像传感器的角度、方向setPreviewOrientation(cameraId)camera.parameters = parameters}}/*** 切换摄像头*/fun switchCamera() {val cameraId = if (cameraId == Camera.CameraInfo.CAMERA_FACING_BACK) {Camera.CameraInfo.CAMERA_FACING_FRONT} else {Camera.CameraInfo.CAMERA_FACING_BACK}switchCamera(cameraId)}/*** 切换摄像头* @param cameraId 指定摄像头ID*/fun switchCamera(cameraId: Int) {this.cameraId = cameraIdpreviewAlign()}private fun previewAlign() {stopPreview()if (surfaceHolder != null) {startPreview(surfaceHolder!!)} else {startPreview(surfaceTexture!!)}}/*** 停止预览*/fun stopPreview() {if (mCamera != null) {mCamera?.setPreviewCallback(null)mCamera?.stopPreview()mCamera?.release()mCamera = null}}/*** 开始预览*/fun startPreview(surfaceHolder: SurfaceHolder) {open(cameraId)this.surfaceHolder = surfaceHolderbuffer = ByteArray(width * height * 3 / 2)bytes = ByteArray(buffer!!.size)//数据缓存区mCamera?.addCallbackBuffer(buffer)mCamera?.setPreviewCallbackWithBuffer(this)//设置预览画面mCamera?.setPreviewDisplay(surfaceHolder)mCamera?.startPreview()}fun startPreview(surfaceTexture: SurfaceTexture) {open(cameraId)this.surfaceTexture = surfaceTexturebuffer = ByteArray(width * height * 3 / 2)bytes = ByteArray(buffer!!.size)//数据缓存区mCamera?.addCallbackBuffer(buffer)mCamera?.setPreviewCallbackWithBuffer(this)//设置预览画面mCamera?.setPreviewTexture(surfaceTexture)mCamera?.startPreview()}private val threadPool = Executors.newCachedThreadPool()/*** 拍摄照片*/fun takePicture(completedCallBack: () -> Unit, errorCallBack: (Exception) -> Unit) {mCamera?.takePicture(null, null, object : Camera.PictureCallback {override fun onPictureTaken(data: ByteArray?, camera: Camera?) {previewAlign()threadPool.execute {val pictureFile: File = getOutputMediaFile(MEDIA_TYPE_IMAGE)!!val bitmap : Bitmaptry {//路径示例 : /storage/emulated/0/Pictures/MyCameraApp/IMG_20230726_135652.jpgbitmap = BitmapFactory.decodeByteArray(data, 0, data!!.size)}catch (e:Exception){errorCallBack.invoke(e)return@execute}val matrix = Matrix()//修正图片方向,这里只是示例,需要根据实际手机方位来决定图片角度matrix.postRotate(if (cameraId == 1) 270F else 90F)if (cameraId == 1) {//postScale在矩阵变换之后进行缩放matrix.postScale(-1F, 1F, bitmap.width / 2F, bitmap.height / 2F)}val rotatedBitmap: Bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)//需要依赖"com.blankj:utilcodex:1.31.1"ImageUtils.save(rotatedBitmap, pictureFile, Bitmap.CompressFormat.JPEG)completedCallBack.invoke()}}})}override fun onPreviewFrame(data: ByteArray, camera: Camera?) {onPreviewListener?.onPreviewFrame(data, camera)camera!!.addCallbackBuffer(data)}private fun setPreviewSize(parameters: Camera.Parameters) {//获取摄像头支持的宽、高val supportedPreviewSizes = parameters.supportedPreviewSizesvar size = supportedPreviewSizes[0]Log.d(TAG, "Camera支持: " + size.width + "x" + size.height)//选择一个与设置的差距最小的支持分辨率var m: Int = Math.abs(size.height * size.width - width * height)supportedPreviewSizes.removeAt(0)val iterator: Iterator<Camera.Size> = supportedPreviewSizes.iterator()//遍历while (iterator.hasNext()) {val next = iterator.next()Log.d(TAG, "支持 " + next.width + "x" + next.height)val n: Int = Math.abs(next.height * next.width - width * height)if (n < m) {m = nsize = next}}width = size.widthheight = size.heightparameters.setPreviewSize(width, height)Log.d(TAG, "预览分辨率 width:" + size.width + " height:" + size.height)}private val mOnChangedSizeListener: OnChangedSizeListener? = nullprivate fun setPreviewOrientation(cameraId: Int) {val info = Camera.CameraInfo()Camera.getCameraInfo(cameraId, info)val rotation = activity.windowManager.defaultDisplay.rotationvar degrees = 0when (rotation) {Surface.ROTATION_0 -> {degrees = 0mOnChangedSizeListener?.onChanged(height, width)}Surface.ROTATION_90 -> {degrees = 90mOnChangedSizeListener?.onChanged(width, height)}Surface.ROTATION_180 -> {degrees = 180mOnChangedSizeListener?.onChanged(height, width)}Surface.ROTATION_270 -> {degrees = 270mOnChangedSizeListener?.onChanged(width, height)}}var result: Intif (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {result = (info.orientation + degrees) % 360result = (360 - result) % 360 // compensate the mirror} else { // back-facingresult = (info.orientation - degrees + 360) % 360}//设置角度, 参考源码注释mCamera!!.setDisplayOrientation(result)}private lateinit var mediaRecorder: MediaRecorderprivate val handle = Handler(Looper.getMainLooper())/*** 开始录像*/fun startVideo(path: String) {mediaRecorder = MediaRecorder()//解锁相机,以供 MediaRecorder 使用mCamera?.unlock()//设置要用于视频捕获的相机mediaRecorder.setCamera(mCamera)//设置音频源mediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER)//设置视频源mediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA)//设置视频的输出格式和编码mediaRecorder.setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH))//设置输出视频播放的方向,这里只是示例,需要根据实际手机方位来决定角度mediaRecorder.setOrientationHint(if (cameraId == 1) 270 else 90)//设置输出文件mediaRecorder.setOutputFile(path)//指定 SurfaceView 预览布局元素mediaRecorder.setPreviewDisplay(surfaceHolder!!.surface)try {mediaRecorder.prepare()} catch (e: IOException) {e.printStackTrace()releaseMediaRecorder()}handle.postDelayed({try {mediaRecorder.start()} catch (e: IOException) {e.printStackTrace()releaseMediaRecorder()}}, 10)}/*** 释放资源*/fun releaseMediaRecorder() {if (mediaRecorder != null) {mediaRecorder.reset() // 清除配置mediaRecorder.release()//mediaRecorder = nullmCamera?.lock()}}/*** 停止录像*/fun stopVideo() {mediaRecorder.stop()mediaRecorder.release()mCamera?.lock()}interface OnChangedSizeListener {fun onChanged(width: Int, height: Int)}interface OnPreviewListener {fun onPreviewFrame(data: ByteArray, camera: Camera?)}private var onPreviewListener: OnPreviewListener? = null/*** 设置预览监听*/fun setOnPreviewListener(listener: OnPreviewListener) {this.onPreviewListener = listener}companion object {private const val TAG = "CAMERA_HELPER"}
}

进行使用

//这里的Activity是横屏的
class MainActivity : AppCompatActivity(), SurfaceHolder.Callback {private lateinit var binding: ActivityMainBindingprivate lateinit var cameraHelper: CameraHelperprivate val cameraId = 1private val nativeLib = NativeLib()override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)binding = ActivityMainBinding.inflate(layoutInflater)setContentView(binding.root)ActivityCompat.requestPermissions(this,arrayOf(Manifest.permission.CAMERA,Manifest.permission.RECORD_AUDIO,Manifest.permission.WRITE_EXTERNAL_STORAGE),1)nativeLib.load(assets, 0, 0)cameraHelper = CameraHelper(this, cameraId,1920,1080)cameraHelper.setOnPreviewListener(object : CameraHelper.OnPreviewListener {override fun onPreviewFrame(data: ByteArray, camera: Camera?) {//预览回调}})binding.surfaceView.holder.addCallback(this)binding.btnTakePicture.setOnClickListener {cameraHelper.takePicture({//拍照成功},{//拍照失败})}binding.btnVideoCapture.setOnClickListener {//开始录制val path = MediaFileUtils.getOutputMediaFile(MediaFileUtils.MEDIA_TYPE_VIDEO)!!.pathcameraHelper.startVideo(path)//cameraHelper.stopVideo() //结束录制}}override fun surfaceCreated(holder: SurfaceHolder) {//开始预览cameraHelper.startPreview(holder)}override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {}override fun surfaceDestroyed(holder: SurfaceHolder) {//停止预览cameraHelper.stopPreview()}
}

7. 附录

7.1 MediaFileUtils

获取媒体文件路径的工具类

object MediaFileUtils {val MEDIA_TYPE_IMAGE = 1val MEDIA_TYPE_VIDEO = 2/** Create a file Uri for saving an image or video */fun getOutputMediaFileUri(type: Int): Uri {return Uri.fromFile(getOutputMediaFile(type))}/** Create a File for saving an image or video */fun getOutputMediaFile(type: Int): File? {// To be safe, you should check that the SDCard is mounted// using Environment.getExternalStorageState() before doing this.val mediaStorageDir = File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES),"MyCameraApp")// This location works best if you want the created images to be shared// between applications and persist after your app has been uninstalled.// Create the storage directory if it does not existmediaStorageDir.apply {if (!exists()) {if (!mkdirs()) {Log.d("MyCameraApp", "failed to create directory")return null}}}// Create a media file nameval timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss").format(Date())return when (type) {MEDIA_TYPE_IMAGE -> {File("${mediaStorageDir.path}${File.separator}IMG_$timeStamp.jpg")}MEDIA_TYPE_VIDEO -> {File("${mediaStorageDir.path}${File.separator}VID_$timeStamp.mp4")}else -> null}}
}

7.2. 本文源码下载

Android Camera1 Demo - 实现预览、拍照、录制视频功能

这篇关于Android 使用Camera1实现相机预览、拍照、录像的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/577810

相关文章

中文分词jieba库的使用与实景应用(一)

知识星球:https://articles.zsxq.com/id_fxvgc803qmr2.html 目录 一.定义: 精确模式(默认模式): 全模式: 搜索引擎模式: paddle 模式(基于深度学习的分词模式): 二 自定义词典 三.文本解析   调整词出现的频率 四. 关键词提取 A. 基于TF-IDF算法的关键词提取 B. 基于TextRank算法的关键词提取

使用SecondaryNameNode恢复NameNode的数据

1)需求: NameNode进程挂了并且存储的数据也丢失了,如何恢复NameNode 此种方式恢复的数据可能存在小部分数据的丢失。 2)故障模拟 (1)kill -9 NameNode进程 [lytfly@hadoop102 current]$ kill -9 19886 (2)删除NameNode存储的数据(/opt/module/hadoop-3.1.4/data/tmp/dfs/na

Hadoop数据压缩使用介绍

一、压缩原则 (1)运算密集型的Job,少用压缩 (2)IO密集型的Job,多用压缩 二、压缩算法比较 三、压缩位置选择 四、压缩参数配置 1)为了支持多种压缩/解压缩算法,Hadoop引入了编码/解码器 2)要在Hadoop中启用压缩,可以配置如下参数

Makefile简明使用教程

文章目录 规则makefile文件的基本语法:加在命令前的特殊符号:.PHONY伪目标: Makefilev1 直观写法v2 加上中间过程v3 伪目标v4 变量 make 选项-f-n-C Make 是一种流行的构建工具,常用于将源代码转换成可执行文件或者其他形式的输出文件(如库文件、文档等)。Make 可以自动化地执行编译、链接等一系列操作。 规则 makefile文件

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

使用opencv优化图片(画面变清晰)

文章目录 需求影响照片清晰度的因素 实现降噪测试代码 锐化空间锐化Unsharp Masking频率域锐化对比测试 对比度增强常用算法对比测试 需求 对图像进行优化,使其看起来更清晰,同时保持尺寸不变,通常涉及到图像处理技术如锐化、降噪、对比度增强等 影响照片清晰度的因素 影响照片清晰度的因素有很多,主要可以从以下几个方面来分析 1. 拍摄设备 相机传感器:相机传

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi

让树莓派智能语音助手实现定时提醒功能

最初的时候是想直接在rasa 的chatbot上实现,因为rasa本身是带有remindschedule模块的。不过经过一番折腾后,忽然发现,chatbot上实现的定时,语音助手不一定会有响应。因为,我目前语音助手的代码设置了长时间无应答会结束对话,这样一来,chatbot定时提醒的触发就不会被语音助手获悉。那怎么让语音助手也具有定时提醒功能呢? 我最后选择的方法是用threading.Time

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo