当前位置: 首页 > news >正文

Android --- CameraX讲解

预备知识

surface   surfaceView   SurfaceHolder

surface 是什么?

一句话来说: surface是一块用于填充图像数据的内存。

surfaceView 是什么?

它是一个显示surface 的View。 在app中仍在 ViewHierachy 中,但在wms 中可以理解为Server端,它与宿主窗口是分离的。(它拥有独立的绘制表面 , 它不与其它宿主窗口共享一个绘制表面,可以单独在一个线程进行绘制,并不会占用主线程的资源,这样,绘制就会比较高效,游戏、视频播放,还有最近几年比较热门的直播,都可以用surface  )

surfaceHolder 是什么?

 它是一个接口,给持有surface的对象使用。可以控制surface的大小和格式。编辑surface中的像素,以及监听surface的变化这个接口通过surfaceView获取。

CameraX

CameraX是Jetpack 支持库

特点:

1、利用的是 camera2 的功能

2、它具有生命周期的感应,使用更加简单,代码量也减少了不少。可以灵活的录制视频和拍照。

3、抹平设备兼容问题。

使用

第一步:

引入依赖

dependencies {def camerax_version = "1.2.0-alpha04"implementation "androidx.camera:camera-core:${camerax_version}"implementation "androidx.camera:camera-camera2:${camerax_version}"implementation "androidx.camera:camera-lifecycle:${camerax_version}"implementation "androidx.camera:camera-video:${camerax_version}"implementation "androidx.camera:camera-view:${camerax_version}"implementation "androidx.camera:camera-extensions:${camerax_version}"
}

使用 ViewBinding,因此请使用以下代码(在 android{} 代码块末尾)启用它:

android {buildFeatures {viewBinding true}
}

第二步:

添加布局控件。   androidx.camera.view.PreViewView

<?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"><androidx.camera.view.PreviewViewandroid:id="@+id/viewFinder"android:layout_width="match_parent"android:layout_height="match_parent" /><Buttonandroid:id="@+id/image_capture_button"android:layout_width="110dp"android:layout_height="110dp"android:layout_marginEnd="50dp"android:layout_marginBottom="50dp"android:elevation="2dp"android:text="@string/take_photo"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintEnd_toStartOf="@id/vertical_centerline" /><Buttonandroid:id="@+id/video_capture_button"android:layout_width="110dp"android:layout_height="110dp"android:layout_marginStart="50dp"android:layout_marginBottom="50dp"android:elevation="2dp"android:text="@string/start_capture"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintStart_toEndOf="@id/vertical_centerline" /><androidx.constraintlayout.widget.Guidelineandroid:id="@+id/vertical_centerline"android:layout_width="wrap_content"android:layout_height="wrap_content"android:orientation="vertical"app:layout_constraintGuide_percent=".50" /></androidx.constraintlayout.widget.ConstraintLayout>

因为 build.gradle 中设置了 viewBinding = true,所以会为每个布局都生成对应的绑定类(即 activity_main.xml 自动生成 ActivityMainBinding 类)。

第三步:

设置检查相机权限:

在 MainActivity.kt 中设置检查相机权限,设置 Button 的响应事件:

package com.bignerdranch.android.cameraxappimport android.Manifest
import android.content.pm.PackageManager
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.camera.core.ImageCapture
import androidx.camera.video.Recorder
import androidx.camera.video.Recording
import androidx.camera.video.VideoCapture
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import com.bignerdranch.android.cameraxapp.databinding.ActivityMainBinding
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executorstypealias LumaListener = (luma: Double) -> Unitclass MainActivity : AppCompatActivity() {private lateinit var viewBinding: ActivityMainBindingprivate var imageCapture: ImageCapture? = nullprivate var videoCapture: VideoCapture<Recorder>? = nullprivate var recording: Recording? = nullprivate lateinit var cameraExecutor: ExecutorServiceoverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)viewBinding = ActivityMainBinding.inflate(layoutInflater)setContentView(viewBinding.root)// Request camera permissionsif (allPermissionsGranted()) {startCamera()} else {ActivityCompat.requestPermissions(this, REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS)}// Set up the listeners for take photo and video capture buttonsviewBinding.imageCaptureButton.setOnClickListener { takePhoto() }viewBinding.videoCaptureButton.setOnClickListener { captureVideo() }cameraExecutor = Executors.newSingleThreadExecutor()}private fun takePhoto() {}private fun captureVideo() {}private fun startCamera() {}private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all {ContextCompat.checkSelfPermission(baseContext, it) == PackageManager.PERMISSION_GRANTED}override fun onDestroy() {super.onDestroy()cameraExecutor.shutdown()}companion object {private const val TAG = "CameraXApp"private const val FILENAME_FORMAT = "yyyy-MM-dd-HH-mm-ss-SSS"private const val REQUEST_CODE_PERMISSIONS = 10private val REQUIRED_PERMISSIONS =mutableListOf(Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO).apply { }.toTypedArray()}
}

在 AndroidManifest.xml 中申请摄像头权限,其中 android.hardware.camera.any 可确保设备配有相机。指定 .any 表示它可以是前置摄像头,也可以是后置摄像头。配置如下:

<uses-feature android:name="android.hardware.camera.any" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"android:maxSdkVersion="28" />

在 MainActivity 添加如下函数,会根据用户批准的权限,执行对应的回调函数:

    override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {super.onRequestPermissionsResult(requestCode, permissions, grantResults)if (requestCode == REQUEST_CODE_PERMISSIONS) {if (allPermissionsGranted()) {startCamera()} else {Toast.makeText(this, "Permissions not granted by the user", Toast.LENGTH_SHORT).show()finish()}}}

第四步:

预览 摄像头、 拍照、 图片分析

初始化cameraProviderFuture 并得到cameraProvider 。

得到cameraProvider 后初始化 PreView 、ImageCapture 、ImageAnalysis。  可以看到三个单独配置的。 所以完全解耦。

 tips:

PreView: 它是相机预览的数据流图像数据通过它来传输,比如输出到surface中。

ImageCapture : 用于拍照 这里的builder 都可以设置参数,比如 大小、曝光度。

ImageAnalysis : 用于图像分析,如二维码识别、人脸等。

预览 摄像头

MainActivity 中实现 startCamera() 函数

    private fun startCamera() {// 用于将相机的生命周期绑定到生命周期所有者(MainActivity)。 这消除了打开和关闭相机的任务,因为 CameraX 具有生命周期感知能力。val cameraProviderFuture = ProcessCameraProvider.getInstance(this)// 向 cameraProviderFuture 添加监听器。添加 Runnable 作为一个参数。我们会在稍后填写它。添加 ContextCompat.getMainExecutor() 作为第二个参数。这将返回一个在主线程上运行的 Executor。cameraProviderFuture.addListener({// 将相机的生命周期绑定到应用进程中的 LifecycleOwner。val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()val preview = Preview.Builder().build().also { it.setSurfaceProvider(viewBinding.viewFinder.surfaceProvider) } // preview 作为 usecaseval cameraSelector = CameraSelector.DEFAULT_BACK_CAMERAtry {cameraProvider.unbindAll() // Unbind use cases before rebindingcameraProvider.bindToLifecycle(this, cameraSelector, preview) // Bind use cases to camera: 把 cameraSelector 和 preview 绑定} catch (exc: Exception) {Log.e(TAG, "Use case binding failed", exc) // 有多种原因可能会导致此代码失败,例如应用不再获得焦点。在此记录日志。}}, ContextCompat.getMainExecutor(this))}

ImageCamera 拍照

 MainActivity 中实现 takePhoto() 函数

    private fun takePhoto() {// Get a stable reference of the modifiable image capture use caseval imageCapture = imageCapture ?: return// 存图路径和参数(时间、文件类型)val name = SimpleDateFormat(FILENAME_FORMAT, Locale.US).format(System.currentTimeMillis())val contentValues = ContentValues().apply {put(MediaStore.MediaColumns.DISPLAY_NAME, name)put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg")}// 我们希望将输出保存在 MediaStore 中,以便其他应用可以显示它val outputOptions =ImageCapture.OutputFileOptions.Builder(contentResolver, MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues).build()// 拍照后的回调函数imageCapture.takePicture(outputOptions, ContextCompat.getMainExecutor(this),object : ImageCapture.OnImageSavedCallback {override fun onError(exc: ImageCaptureException) {Log.e(TAG, "Photo capture failed: ${exc.message}", exc)}override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {val msg = "Photo capture succeeded: ${outputFileResults.savedUri}"Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show()Log.d(TAG, msg)}})}

在 MainActivity 的 startCamera() 函数中,添加如下 imageCapture = ImageCapture.Builder().build() 来初始化摄像头的 use case,并绑定到 cameraProvider.bindToLifecycle() 中,完整代码如下。

    private fun startCamera() {// 用于将相机的生命周期绑定到生命周期所有者(MainActivity)。 这消除了打开和关闭相机的任务,因为 CameraX 具有生命周期感知能力。val cameraProviderFuture = ProcessCameraProvider.getInstance(this)// 向 cameraProviderFuture 添加监听器。添加 Runnable 作为一个参数。我们会在稍后填写它。添加 ContextCompat.getMainExecutor() 作为第二个参数。这将返回一个在主线程上运行的 Executor。cameraProviderFuture.addListener({// 将相机的生命周期绑定到应用进程中的 LifecycleOwner。val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()val preview = Preview.Builder().build().also { it.setSurfaceProvider(viewBinding.viewFinder.surfaceProvider) } // preview 作为 usecaseimageCapture = ImageCapture.Builder().build()val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERAtry {cameraProvider.unbindAll() // Unbind use cases before rebindingcameraProvider.bindToLifecycle(this, cameraSelector, preview, imageCapture) // Bind use cases to camera} catch (exc: Exception) {Log.e(TAG, "Use case binding failed", exc) // 有多种原因可能会导致此代码失败,例如应用不再获得焦点。在此记录日志。}}, ContextCompat.getMainExecutor(this))}

用 ImageAnalysis 分析各帧

使用 ImageAnalysis 功能可让相机应用变得更加有趣。它允许定义实现 ImageAnalysis.Analyzer 接口的自定义类,并使用传入的相机帧调用该类。我们无需管理相机会话状态,甚至无需处理图像;与其他生命周期感知型组件一样,仅绑定到应用所需的生命周期就足够了。

    private class LuminosityAnalyzer(private val listener: LumaListener) : ImageAnalysis.Analyzer {private fun ByteBuffer.toByteArray(): ByteArray {rewind()    // Rewind the buffer to zeroval data = ByteArray(remaining())get(data)   // Copy the buffer into a byte arrayreturn data // Return the byte array}override fun analyze(image: ImageProxy) {val buffer = image.planes[0].bufferval data = buffer.toByteArray()val pixels = data.map { it.toInt() and 0xFF }val luma = pixels.average()listener(luma)image.close()}}

然后,在 startCamera() 函数中,实例化 imageAnalyzer 对象,通过 setAnalyzer() 设置其回调函数来打印 luma(亮度),并绑定到 cameraProvider.bindToLifecycle() 上,代码如下:

    private fun startCamera() {// 用于将相机的生命周期绑定到生命周期所有者(MainActivity)。 这消除了打开和关闭相机的任务,因为 CameraX 具有生命周期感知能力。val cameraProviderFuture = ProcessCameraProvider.getInstance(this)// 向 cameraProviderFuture 添加监听器。添加 Runnable 作为一个参数。我们会在稍后填写它。添加 ContextCompat.getMainExecutor() 作为第二个参数。这将返回一个在主线程上运行的 Executor。cameraProviderFuture.addListener({// 将相机的生命周期绑定到应用进程中的 LifecycleOwner。val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()val preview = Preview.Builder().build().also { it.setSurfaceProvider(viewBinding.viewFinder.surfaceProvider) } // preview 作为 usecaseimageCapture = ImageCapture.Builder().build()val imageAnalyzer = ImageAnalysis.Builder().build().also {it.setAnalyzer(cameraExecutor, LuminosityAnalyzer { luma ->Log.d(TAG, "Average luminosity: $luma")})}val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERAtry {cameraProvider.unbindAll() // Unbind use cases before rebindingcameraProvider.bindToLifecycle(this, cameraSelector, preview, imageCapture, imageAnalyzer) // Bind use cases to camera} catch (exc: Exception) {Log.e(TAG, "Use case binding failed", exc) // 有多种原因可能会导致此代码失败,例如应用不再获得焦点。在此记录日志。}}, ContextCompat.getMainExecutor(this))}

其实是通过 LuminosityAnalyzer.analyze() 函数内的 listener(luma) 将 luma 参数传给 listener() 函数,然后我们通过 setAnalyzer() 自定义了 listener() 函数,其接收亮度,并通过 Logcat 打印。

用 VideoCapture 录像

 MainActivity 中实现 captureVideo() 

    private fun captureVideo() {val videoCapture = this.videoCapture ?: returnviewBinding.videoCaptureButton.isEnabled = falseval curRecording = recordingif (curRecording != null) {curRecording.stop()recording = nullreturn}// create and start a new recording sessionval name = SimpleDateFormat(FILENAME_FORMAT, Locale.US).format(System.currentTimeMillis())val contentValues = ContentValues().apply {put(MediaStore.MediaColumns.DISPLAY_NAME, name)put(MediaStore.MediaColumns.MIME_TYPE, "video/mp4")}val mediaStoreOutputOptions = MediaStoreOutputOptions.Builder(contentResolver, MediaStore.Video.Media.EXTERNAL_CONTENT_URI).setContentValues(contentValues).build()recording = videoCapture.output.prepareRecording(this, mediaStoreOutputOptions).apply {if (PermissionChecker.checkSelfPermission(this@MainActivity, Manifest.permission.RECORD_AUDIO) == PermissionChecker.PERMISSION_GRANTED) {withAudioEnabled()}}.start(ContextCompat.getMainExecutor(this)) { recordEvent ->when (recordEvent) {is VideoRecordEvent.Start -> {viewBinding.videoCaptureButton.apply {text = getString(R.string.stop_capture)isEnabled = true}}is VideoRecordEvent.Finalize -> {if (!recordEvent.hasError()) {val msg = "Video capture succeeded: ${recordEvent.outputResults.outputUri}"Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show()Log.d(TAG, msg)} else {recording?.close()recording = nullLog.e(TAG, "Video capture ends with error: ${recordEvent.error}")}viewBinding.videoCaptureButton.apply {text = getString(R.string.start_capture)isEnabled = true}}}}}

然后,在 MainActivity 的 startCamera() 函数中,将 videoCapture 绑定到 cameraProvider.bindToLifecycle() 函数中,因为camera 同时只能绑定3种use case,所以本节在拍照、摄像、预览、分析中,选择了前3种用途,

   private fun startCamera() {// 用于将相机的生命周期绑定到生命周期所有者(MainActivity)。 这消除了打开和关闭相机的任务,因为 CameraX 具有生命周期感知能力。val cameraProviderFuture = ProcessCameraProvider.getInstance(this)// 向 cameraProviderFuture 添加监听器。添加 Runnable 作为一个参数。我们会在稍后填写它。添加 ContextCompat.getMainExecutor() 作为第二个参数。这将返回一个在主线程上运行的 Executor。cameraProviderFuture.addListener({// 将相机的生命周期绑定到应用进程中的 LifecycleOwner。val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()val preview = Preview.Builder().build().also { it.setSurfaceProvider(viewBinding.viewFinder.surfaceProvider) } // preview 作为 use caseimageCapture = ImageCapture.Builder().build()
//            val imageAnalyzer = ImageAnalysis.Builder().build().also {
//                it.setAnalyzer(cameraExecutor, LuminosityAnalyzer { luma ->
//                    Log.d(TAG, "Average luminosity: $luma")
//                })
//            }val recorder = Recorder.Builder().setQualitySelector(QualitySelector.from(Quality.HIGHEST)).build()videoCapture = VideoCapture.withOutput(recorder)val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERAtry {cameraProvider.unbindAll() // Unbind use cases before rebindingcameraProvider.bindToLifecycle(this, cameraSelector, preview, imageCapture, videoCapture) // Bind use cases to camera} catch (exc: Exception) {Log.e(TAG, "Use case binding failed", exc) // 有多种原因可能会导致此代码失败,例如应用不再获得焦点。在此记录日志。}}, ContextCompat.getMainExecutor(this))}

​​​​​​​

结束。

参考: 

https://juejin.cn/post/7354922285092847668

https://juejin.cn/post/7354937037391986722

使用Preview预览和自定义SurfaceView或者TextureView实现CameraX预览功能_preview.setsurfaceprovider-CSDN博客

Android之SurfaceView和TextureView的分析_surfaceview和textview的区别-CSDN博客​​​​​​​
 

在Android CameraX中,PreviewView和SurfaceView的关系主要体现在它们都可以作为相机的预览显示视图,但PreviewView提供了更多的功能和灵活性。

PreviewView和SurfaceView的区别和联系

  1. 功能差异‌:

    • PreviewView‌:这是一个专门为CameraX设计的视图,支持裁剪、缩放和旋转,确保预览的正确显示。它提供了更多的控制和灵活性,例如可以设置不同的预览分辨率和帧率‌1。
    • SurfaceView‌:是一个更传统的视图,用于展示相机预览图像。它不需要布局文件中的声明,可以直接在代码中创建和使用。SurfaceView适合简单的预览需求,但在CameraX中,PreviewView是更推荐的选择‌12。
  2. 使用场景‌:

    • PreviewView‌:适用于需要高度自定义预览显示的场景,如实时视频处理、预览画面的特殊效果等。它能够更好地与CameraX的其他组件集成,提供更好的用户体验‌1。
    • SurfaceView‌:适用于简单的预览需求,不需要复杂的预览处理。虽然SurfaceView在CameraX中不是首选,但在一些旧的项目或特定的需求中仍然可以使用‌12。

如何在CameraX中使用PreviewView

  1. 添加PreviewView到布局文件‌:

     

    xmlCopy Code

    <FrameLayout android:id="@+id/container" android:layout_width="match_parent" android:layout_height="match_parent"> <androidx.camera.view.PreviewView android:id="@+id/previewView" android:layout_width="match_parent" android:layout_height="match_parent"/> </FrameLayout>

  2. 初始化ProcessCameraProvider并绑定PreviewView‌:

     

    kotlinCopy Code

    override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) if (savedInstanceState == null) {

Android OpenGL ES 学习(一) -- 基本概念-CSDN博客

Android 音视频编解码(一) -- MediaCodec 初探_安卓音视频解码-CSDN博客

相关文章:

Android --- CameraX讲解

预备知识 surface surfaceView SurfaceHolder surface 是什么&#xff1f; 一句话来说&#xff1a; surface是一块用于填充图像数据的内存。 surfaceView 是什么&#xff1f; 它是一个显示surface 的View。 在app中仍在 ViewHierachy 中&#xff0c;但在wms 中可以理解为…...

ElasticSearch view

基础知识类 elasticsearch和数据库之间区别&#xff1f; elasticsearch&#xff1a;面向文档&#xff0c;数据以文档的形式存储&#xff0c;即JSON格式的对象。更强调数据的搜索、索引和分析。 数据库&#xff1a;更侧重于事务处理、数据的严格结构化和完整性&#xff0c;适用于…...

list的使用,及部分功能的模拟实现(C++)

目录&#xff08;文章中"节点"和"结点"是同一个意思&#xff09; 1. list的介绍及使用 1.1 list的介绍 1.2 list的使用 1.2.1 list的构造 1.2.2 list iterator的使用 1.2.3 list capacity 1.2.4 list element access 1.2.5 list modifiers 1.2.6 list…...

联想Y7000+RTX4060+i7+Ubuntu22.04运行DeepSeek开源多模态大模型Janus-Pro-1B+本地部署

直接上手搓了&#xff1a; conda create -n myenv python3.10 -ygit clone https://github.com/deepseek-ai/Janus.gitcd Januspip install -e .pip install webencodings beautifulsoup4 tinycss2pip install -e .[gradio]pip install pexpect>4.3python demo/app_januspr…...

[Spring] Gateway详解

&#x1f338;个人主页:https://blog.csdn.net/2301_80050796?spm1000.2115.3001.5343 &#x1f3f5;️热门专栏: &#x1f9ca; Java基本语法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12615970.html?spm1001.2014.3001.5482 &#x1f355; Collection与…...

音叉模态分析

目录 0 序言 1 自由状态下模态求解 1.1 添加模态项目 1.2 生成网格 1.3 设置最大模态阶数 1.4 求解 1.5 结果查看 1.6 结果分析 2 音叉能否释放频率440Hz的音调 3 预应力模态求解 3.1 静态结构分析 3.1.1 添加静态结构项目 3.1.2生成网格 3.1.3添加边界条件 3.1…...

BW AO/工作簿权限配置

场景&#xff1a; 按事业部配置工作簿权限&#xff1b; 1、创建用户 事务码&#xff1a;SU01&#xff0c;用户主数据的维护&#xff0c;可以创建、修改、删除、锁定、解锁、修改密码等 用户设置详情页 2、创建权限角色 用户的权限菜单是通过权限角色分配来实现的 2.1、自定…...

C++ 字母大小写转换两种方法统计数字字符的个数

目录 题目&#xff1a; 代码1&#xff1a; 代码2&#xff1a; 题目描述输入一行字符&#xff0c;统计出其中数字字符的个数。 代码如下&#xff1a; 判断⼀个字符是否是数字字符有⼀个函数是 isdigit ,可以直接使⽤。 代码如下&#xff1a; 题目&#xff1a; 大家都知道…...

如何使用 ChatBox AI 简化本地模型对话操作

部署模型请看上一篇帖子&#xff1a;本地部署DeepSeek教程&#xff08;Mac版本&#xff09;-CSDN博客 使用 ChatBox AI 简化本地模型对话操作&#xff1a; 打开 ChatBox AI 官网&#xff1a;Chatbox AI官网&#xff1a;办公学习的AI好助手&#xff0c;全平台AI客户端&#xf…...

前端面试笔试题目(一)

以下模拟了大厂前端面试流程&#xff0c;并给出了涵盖HTML、CSS、JavaScript等基础和进阶知识的前端笔试题目&#xff0c;以帮助你更好地准备面试。 面试流程模拟 1. 自我介绍&#xff08;5 - 10分钟&#xff09;&#xff1a;面试官会请你进行简单的自我介绍&#xff0c;包括…...

Docker Hello World

Docker Hello World 引言 Docker 是一个开源的应用容器引擎,可以让开发者打包他们的应用以及应用的依赖包到一个可移植的容器中,然后发布到任何流行的 Linux 机器上,也可以实现虚拟化。本文将带领您从零开始,学习如何使用 Docker 运行一个简单的 "Hello World"…...

UE 5.3 C++ 对垃圾回收的初步认识

一.UObject的创建 UObject 不支持构造参数。 所有的C UObject都会在引擎启动的时候初始化&#xff0c;然后引擎会调用其默认构造器。如果没有默认的构造器&#xff0c;那么 UObject 将不会编译。 有修改父类参数的需求&#xff0c;就使用指定带参构造 // Sets default value…...

ARM内核:嵌入式时代的核心引擎

引言 在当今智能设备无处不在的时代&#xff0c;ARM&#xff08;Advanced RISC Machines&#xff09;处理器凭借其高性能、低功耗的特性&#xff0c;成为智能手机、物联网设备、汽车电子等领域的核心引擎。作为精简指令集&#xff08;RISC&#xff09;的典范&#xff0c;ARM核…...

需求分析应该从哪些方面来着手做?

需求分析一般可从以下几个方面着手&#xff1a; 业务需求方面 - 与相关方沟通&#xff1a;与业务部门、客户等进行深入交流&#xff0c;通过访谈、问卷调查、会议讨论等方式&#xff0c;明确他们对项目的期望、目标和整体业务需求&#xff0c;了解项目要解决的业务问题及达成的…...

【Unity2D 2022:C#Script】DoTween插件的使用

一、插件介绍 DOTween 是一个快速、高效、完全类型安全的 Unity 面向对象的动画引擎&#xff0c;针对 C# 用户进行了优化&#xff0c;免费和开源&#xff0c;具有大量高级功能 二、插件的下载 1. DoTween官网&#xff1a;DOTween (HOTween v2) 2. DoTween下载&#xff1a; …...

【Docker】ubuntu中 Docker的使用

之前记录了 docker的安装 【环境配置】ubuntu中 Docker的安装&#xff1b; 本篇博客记录Dockerfile的示例&#xff0c;docker 的使用&#xff0c;包括镜像的构建、容器的启动、docker compose的使用等。   当安装好后&#xff0c;可查看docker的基本信息 docker info ## 查…...

【数据结构篇】时间复杂度

一.数据结构前言 1.1 数据结构的概念 数据结构(Data Structure)是计算机存储、组织数据的⽅式&#xff0c;指相互之间存在⼀种或多种特定关系的数 据元素的集合。没有⼀种单⼀的数据结构对所有⽤途都有⽤&#xff0c;所以我们要学各式各样的数据结构&#xff0c; 如&#xff1a…...

linux 环境安装 dlib 的 gpu 版本

默认使用 pip 安装的 dlib 是不使用 gpu 的 在国内社区用百度查如何安装 gpu 版本的 dlib 感觉信息都不太对&#xff0c;都是说要源码编译还有点复杂 还需要自己安装 cuda 相关的包啥的&#xff0c;看着就头大 于是想到这个因该 conda 自己就支持了吧&#xff0c;然后查了一下…...

springboot集成钉钉,发送钉钉日报

目录 1.说明 2.示例 3.总结 1.说明 学习地图 - 钉钉开放平台 在钉钉开放文档中可以查看有关日志相关的api&#xff0c;主要用到以下几个api&#xff1a; ①获取模板详情 ②获取用户发送日志的概要信息 ③获取日志接收人员列表 ④创建日志 发送日志时需要根据模板规定日志…...

【机器学习】自定义数据集 使用scikit-learn中svm的包实现svm分类

一、支持向量机(support vector machines. &#xff0c;SVM)概念 1. SVM 绪论 支持向量机&#xff08;SVM&#xff09;的核心思想是找到一个最优的超平面&#xff0c;将不同类别的数据点分开。SVM 的关键特点包括&#xff1a; ① 分类与回归&#xff1a; SVM 可以用于分类&a…...

ES6从入门到精通:前言

ES6简介 ES6&#xff08;ECMAScript 2015&#xff09;是JavaScript语言的重大更新&#xff0c;引入了许多新特性&#xff0c;包括语法糖、新数据类型、模块化支持等&#xff0c;显著提升了开发效率和代码可维护性。 核心知识点概览 变量声明 let 和 const 取代 var&#xf…...

Qt/C++开发监控GB28181系统/取流协议/同时支持udp/tcp被动/tcp主动

一、前言说明 在2011版本的gb28181协议中&#xff0c;拉取视频流只要求udp方式&#xff0c;从2016开始要求新增支持tcp被动和tcp主动两种方式&#xff0c;udp理论上会丢包的&#xff0c;所以实际使用过程可能会出现画面花屏的情况&#xff0c;而tcp肯定不丢包&#xff0c;起码…...

STM32+rt-thread判断是否联网

一、根据NETDEV_FLAG_INTERNET_UP位判断 static bool is_conncected(void) {struct netdev *dev RT_NULL;dev netdev_get_first_by_flags(NETDEV_FLAG_INTERNET_UP);if (dev RT_NULL){printf("wait netdev internet up...");return false;}else{printf("loc…...

理解 MCP 工作流:使用 Ollama 和 LangChain 构建本地 MCP 客户端

&#x1f31f; 什么是 MCP&#xff1f; 模型控制协议 (MCP) 是一种创新的协议&#xff0c;旨在无缝连接 AI 模型与应用程序。 MCP 是一个开源协议&#xff0c;它标准化了我们的 LLM 应用程序连接所需工具和数据源并与之协作的方式。 可以把它想象成你的 AI 模型 和想要使用它…...

Module Federation 和 Native Federation 的比较

前言 Module Federation 是 Webpack 5 引入的微前端架构方案&#xff0c;允许不同独立构建的应用在运行时动态共享模块。 Native Federation 是 Angular 官方基于 Module Federation 理念实现的专为 Angular 优化的微前端方案。 概念解析 Module Federation (模块联邦) Modul…...

论文解读:交大港大上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(一)

宇树机器人多姿态起立控制强化学习框架论文解析 论文解读&#xff1a;交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架&#xff08;一&#xff09; 论文解读&#xff1a;交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化…...

Ascend NPU上适配Step-Audio模型

1 概述 1.1 简述 Step-Audio 是业界首个集语音理解与生成控制一体化的产品级开源实时语音对话系统&#xff0c;支持多语言对话&#xff08;如 中文&#xff0c;英文&#xff0c;日语&#xff09;&#xff0c;语音情感&#xff08;如 开心&#xff0c;悲伤&#xff09;&#x…...

鱼香ros docker配置镜像报错:https://registry-1.docker.io/v2/

使用鱼香ros一件安装docker时的https://registry-1.docker.io/v2/问题 一键安装指令 wget http://fishros.com/install -O fishros && . fishros出现问题&#xff1a;docker pull 失败 网络不同&#xff0c;需要使用镜像源 按照如下步骤操作 sudo vi /etc/docker/dae…...

RNN避坑指南:从数学推导到LSTM/GRU工业级部署实战流程

本文较长&#xff0c;建议点赞收藏&#xff0c;以免遗失。更多AI大模型应用开发学习视频及资料&#xff0c;尽在聚客AI学院。 本文全面剖析RNN核心原理&#xff0c;深入讲解梯度消失/爆炸问题&#xff0c;并通过LSTM/GRU结构实现解决方案&#xff0c;提供时间序列预测和文本生成…...

HashMap中的put方法执行流程(流程图)

1 put操作整体流程 HashMap 的 put 操作是其最核心的功能之一。在 JDK 1.8 及以后版本中&#xff0c;其主要逻辑封装在 putVal 这个内部方法中。整个过程大致如下&#xff1a; 初始判断与哈希计算&#xff1a; 首先&#xff0c;putVal 方法会检查当前的 table&#xff08;也就…...