006-Jetpack Compose for Android之传感器数据

news2025/1/5 7:55:59

在这里插入图片描述
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

需求分析

想要看看手机的传感器数据,看看滤波一下能玩点什么无聊的。先搞个最简单的,手机本身的姿态。

需求:采集手机姿态数据,显示在界面上。

那么我们需要:

  • 一个文本标签类似的控件,显示手机姿态数据,三个角度:pitch, roll, yaw
  • 是不是需要做一个图标?显示姿态的变化?
  • 这样就提出了需要一个时间标签,显示采集数据的时间(间隔)
  • 开始/停止采集数据的按钮是否需要?在这个场景,单一功能,不需要,把软件打开和软件关闭作为采集数据的开始和停止。
  • 数据如何导出?肯定是需要的,那么我们考虑导出csv文件。

核心数据

  • 时间序列,(t, pitch, roll, yaw)
  • 采集间隔, d t dt dt,由硬件确定?

用户交互

  • 打开程序
  • 关闭程序
  • 导出数据

界面设计

大概我们可以在上方设置一个标签,显示实时得到的最新数据,下方主体部分一个图标,动态更新,显示姿态的变化。

实现流程

建立工程

打开Androi的Studio,新建一个项目,选择Jetpack Compose模板。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

记得要认准这个中间的Compose图标。

然后否就是一顿修改镜像地址。首先是gradle下载地址,修改gradle/wrapper/gradle-wrapper.properties文件:

#Fri Dec 13 22:34:09 CST 2024
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://mirrors.aliyun.com/gradle/distributions/v8.9.0/gradle-8.9-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

接下来就是修改settings.gradle.kts文件,增加下载地址:

pluginManagement {
    repositories {
        maven { url = uri("https://maven.aliyun.com/repository/public/") }
        google {
            content {
                includeGroupByRegex("com\\.android.*")
                includeGroupByRegex("com\\.google.*")
                includeGroupByRegex("androidx.*")
            }
        }
        mavenCentral()
        gradlePluginPortal()
    }
}
dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        maven { url = uri("https://maven.aliyun.com/repository/public/") }
        google()
        mavenCentral()
        maven { url = uri("https://jitpack.io") }
    }
}

rootProject.name = "YawPitchRoll"
include(":app")

只有经过了上面两步,才能什么同步Gradle 工程之类的,然后build一下,确认所有的依赖都下载完了。可以稍微运行一下也没问题。

建立界面

建立界面在Jetpack中间很简单很直观。

package org.cardc.fdii.qc.Instruments

import android.app.AlertDialog
import android.content.Context
import android.content.Intent
import android.hardware.Sensor
import android.hardware.SensorEvent
import android.hardware.SensorEventListener
import android.hardware.SensorManager
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.view.MotionEvent
import android.widget.EditText
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.annotation.RequiresApi
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableLongStateOf
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import com.github.mikephil.charting.charts.LineChart
import com.github.mikephil.charting.components.XAxis
import com.github.mikephil.charting.components.YAxis
import com.github.mikephil.charting.data.Entry
import com.github.mikephil.charting.data.LineData
import com.github.mikephil.charting.data.LineDataSet
import com.github.mikephil.charting.listener.ChartTouchListener
import com.github.mikephil.charting.listener.OnChartGestureListener
import com.github.mikephil.charting.utils.ColorTemplate
import org.cardc.fdii.qc.Instruments.ui.theme.FirstApplicationTheme
import java.io.File
import java.io.FileWriter

@Composable
fun SensorChart(
    yawData: List<Entry>,
    pitchData: List<Entry>,
    rollData: List<Entry>,
    modifier: Modifier = Modifier
) {
    val context = LocalContext.current
    val chart = remember { LineChart(context) }

    val yawDataSet = LineDataSet(yawData, "Yaw").apply {
        lineWidth = 2f
        color = ColorTemplate.COLORFUL_COLORS[0]
        axisDependency = YAxis.AxisDependency.LEFT
    }

    val pitchDataSet = LineDataSet(pitchData, "Pitch").apply {
        lineWidth = 2f

        color = ColorTemplate.COLORFUL_COLORS[1]
        axisDependency = YAxis.AxisDependency.LEFT
    }

    val rollDataSet = LineDataSet(rollData, "Roll").apply {
        lineWidth = 2f

        color = ColorTemplate.COLORFUL_COLORS[2]
        axisDependency = YAxis.AxisDependency.LEFT
    }

    val lineData = LineData(yawDataSet, pitchDataSet, rollDataSet)
    chart.data = lineData

    chart.xAxis.position = XAxis.XAxisPosition.BOTTOM
    chart.axisRight.isEnabled = false
    chart.description.isEnabled = false


    // Set gesture listener
    chart.onChartGestureListener = object : OnChartGestureListener {
        override fun onChartGestureStart(
            me: MotionEvent?, lastPerformedGesture: ChartTouchListener.ChartGesture?
        ) {
        }

        override fun onChartGestureEnd(
            me: MotionEvent?, lastPerformedGesture: ChartTouchListener.ChartGesture?
        ) {
        }

        override fun onChartLongPressed(me: MotionEvent?) {}

        @RequiresApi(Build.VERSION_CODES.O)
        override fun onChartDoubleTapped(me: MotionEvent?) {
            showFileNameDialog(context, yawData, pitchData, rollData)
        }

        override fun onChartSingleTapped(me: MotionEvent?) {}
        override fun onChartFling(
            me1: MotionEvent?, me2: MotionEvent?, velocityX: Float, velocityY: Float
        ) {
        }

        override fun onChartScale(me: MotionEvent?, scaleX: Float, scaleY: Float) {}
        override fun onChartTranslate(me: MotionEvent?, dX: Float, dY: Float) {}
    }

    chart.invalidate()
    // Enable auto-scaling
    chart.isAutoScaleMinMaxEnabled = true

    AndroidView({ chart }, modifier = modifier.padding(16.dp).border(1.dp, Color.Gray))
}


class MainActivity : ComponentActivity(), SensorEventListener {
    private lateinit var sensorManager: SensorManager
    private var rotationVectorSensor: Sensor? = null

    private var _yaw by mutableFloatStateOf(0f)
    private var _pitch by mutableFloatStateOf(0f)
    private var _roll by mutableFloatStateOf(0f)

    // add a variable to store the high resolution time
    private val _time0 = System.nanoTime()
    private var _time by mutableLongStateOf(0L)

    override fun onResume() {
        super.onResume()
        rotationVectorSensor?.also { sensor ->
            sensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_NORMAL)
        }
    }

    override fun onPause() {
        super.onPause()
        sensorManager.unregisterListener(this)
    }

    override fun onSensorChanged(event: SensorEvent?) {
        event?.let {
            if (it.sensor.type == Sensor.TYPE_ROTATION_VECTOR) {
                val rotationMatrix = FloatArray(9)
                SensorManager.getRotationMatrixFromVector(rotationMatrix, it.values)
                val orientation = FloatArray(3)
                SensorManager.getOrientation(rotationMatrix, orientation)
                _yaw = Math.toDegrees(orientation[0].toDouble()).toFloat()
                _pitch = Math.toDegrees(orientation[1].toDouble()).toFloat()
                _roll = Math.toDegrees(orientation[2].toDouble()).toFloat()
                // update the time
                _time = System.nanoTime() - _time0
            }
        }
    }

    override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {
        // Do nothing
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        sensorManager = getSystemService(SENSOR_SERVICE) as SensorManager
        rotationVectorSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR)


        setContent {
            FirstApplicationTheme {
                Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
                    SensorDataDisplay(
                        yaw = _yaw,
                        pitch = _pitch,
                        roll = _roll,
                        t = _time,
                        modifier = Modifier.padding(innerPadding)
                    )

                }
            }
        }
    }
}


@Composable
fun SensorDataDisplay(
    yaw: Float, pitch: Float, roll: Float, t: Long, modifier: Modifier = Modifier
) {
    val yawData = remember { mutableStateListOf<Entry>() }
    val pitchData = remember { mutableStateListOf<Entry>() }
    val rollData = remember { mutableStateListOf<Entry>() }
    if (t > 0) {
        yawData.add(Entry(t * 1e-9f, yaw))
        pitchData.add(Entry(t * 1e-9f, pitch))
        rollData.add(Entry(t * 1e-9f, roll))
    }
    Column(modifier = modifier) {
        val context = LocalContext.current
        Text(
            text = "qchen2015@hotmail.com © 2024",
            modifier = Modifier
                .padding(6.dp)
                .fillMaxWidth(),
            textAlign = TextAlign.Center
        )
        // add a hyperlink to the author's website
        Text(
            text = "https://www.windtunnel.cn",
            modifier = Modifier
                .padding(6.dp)
                .fillMaxWidth()
                .clickable {
                    val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://www.windtunnel.cn/categories/jetpack/"))
                    context.startActivity(intent)
                },
            textAlign = TextAlign.Center,
            color = Color.Blue,
            style = TextStyle(textDecoration = TextDecoration.Underline)
        )


        Text(
            text = "Yaw  : %16.4f°\nPitch: %16.4f°\nRoll  : %16.4f°\nTime: %16.6fs"
                .format(yaw, pitch, roll, t * 1e-9),
            modifier = Modifier.padding(16.dp)
        )
        SensorChart(yawData, pitchData, rollData, modifier = Modifier.fillMaxSize())
        // add an about button to show author information

    }
}


@RequiresApi(Build.VERSION_CODES.O)
fun showFileNameDialog(
    context: Context, yawData: List<Entry>, pitchData: List<Entry>, rollData: List<Entry>
) {
    val editText = EditText(context).apply {
        setHint("Enter file name")
        // get date and time
        val currentDateTime = java.time.LocalDateTime.now()
        val formatter = java.time.format.DateTimeFormatter.ofPattern("yyyyMMddHHmmss")
        setText(currentDateTime.format(formatter))
    }
    val dialog = AlertDialog.Builder(context).setTitle("Enter file name").setView(editText)
        .setPositiveButton("Save") { _, _ ->
            val fileName = editText.text.toString()
            if (fileName.isNotEmpty()) {
                saveDataToCsv(context, fileName, yawData, pitchData, rollData)
            } else {
                Toast.makeText(context, "File name cannot be empty", Toast.LENGTH_SHORT).show()
            }
        }.setNegativeButton("Cancel", null).create()
    dialog.show()
}

fun saveDataToCsv(
    context: Context,
    fileName: String,
    yawData: List<Entry>,
    pitchData: List<Entry>,
    rollData: List<Entry>
) {
    val file = File(context.getExternalFilesDir(null), "${fileName.trim()}.csv")
    FileWriter(file).use { writer ->
        writer.append("Time,Yaw,Pitch,Roll\n")
        for (i in yawData.indices) {
            writer.append("${yawData[i].x},${yawData[i].y},${pitchData[i].y},${rollData[i].y}\n")
        }
    }
    Toast.makeText(context, "Data saved to ${file.absolutePath}", Toast.LENGTH_SHORT).show()
}

这里面自己写的代码几乎没有,就是把MainActivity增加了一个继承SensorEventListener的接口,然后增加了一个SensorManager的实例,传感器Sensor实例,还有三个角度的数据、时间零点和当前时间。

SensorEventListener的接口要求实现几个方法:

  • onResume,注册传感器监听器
  • onPause,取消注册传感器监听器
  • onSensorChanged,传感器数据变化时调用
  • onAccuracyChanged,传感器精度变化时调用,这里我们不关心

MainActivityonCreate方法中,我们初始化了传感器管理和传感器实例。在setContent中,我们在Scaffold中增加了一个SensorDataDisplay的组件,这个组件是我们自己写的,用来显示传感器数据。

在这个SensorDataDisplay组件中,我们组织了一个Column,整个都是简单直观。

对于组件的输入变量,我们采用了remember的方式,这样可以在组件内部保存状态。当更新组件角度时,奖结果存入mutableStateListOf<Entry>中,这个EntryMPAndroidChart库中的数据结构,用来存储图表数据。

第一行是一个版权信息,第二行稍微有一点意思,是一个可以点击的Text,会访问本站。

    // add a hyperlink to the author's website
    Text(
        text = "https://www.windtunnel.cn",
        modifier = Modifier
            .padding(6.dp)
            .fillMaxWidth()
            .clickable {
                val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://www.windtunnel.cn/categories/jetpack/"))
                context.startActivity(intent)
            },
        textAlign = TextAlign.Center,
        color = Color.Blue,
        style = TextStyle(textDecoration = TextDecoration.Underline)
    )

Android这一点就挺好,只要用Intent就可以打开浏览器,不用自己写什么复杂的东西。

第三行就是角度标签:

    Text(
        text = "Yaw  : %16.4f°\nPitch: %16.4f°\nRoll  : %16.4f°\nTime: %16.6fs"
            .format(yaw, pitch, roll, t * 1e-9),
        modifier = Modifier.padding(16.dp)
    )

第四行,是一个采用开源图标库MPAndroidChartLineChart来实现的SensorChart,用来显示角度变化。

    SensorChart(yawData, pitchData, rollData, modifier = Modifier.fillMaxSize())
@Composable
fun SensorChart(
    yawData: List<Entry>,
    pitchData: List<Entry>,
    rollData: List<Entry>,
    modifier: Modifier = Modifier
) {
    val context = LocalContext.current
    val chart = remember { LineChart(context) }

    val yawDataSet = LineDataSet(yawData, "Yaw").apply {
        lineWidth = 2f
        color = ColorTemplate.COLORFUL_COLORS[0]
        axisDependency = YAxis.AxisDependency.LEFT
    }

    val pitchDataSet = LineDataSet(pitchData, "Pitch").apply {
        lineWidth = 2f

        color = ColorTemplate.COLORFUL_COLORS[1]
        axisDependency = YAxis.AxisDependency.LEFT
    }

    val rollDataSet = LineDataSet(rollData, "Roll").apply {
        lineWidth = 2f

        color = ColorTemplate.COLORFUL_COLORS[2]
        axisDependency = YAxis.AxisDependency.LEFT
    }

    val lineData = LineData(yawDataSet, pitchDataSet, rollDataSet)
    chart.data = lineData

    chart.xAxis.position = XAxis.XAxisPosition.BOTTOM
    chart.axisRight.isEnabled = false
    chart.description.isEnabled = false


    // Set gesture listener
    chart.onChartGestureListener = object : OnChartGestureListener {
        override fun onChartGestureStart(
            me: MotionEvent?, lastPerformedGesture: ChartTouchListener.ChartGesture?
        ) {
        }

        override fun onChartGestureEnd(
            me: MotionEvent?, lastPerformedGesture: ChartTouchListener.ChartGesture?
        ) {
        }

        override fun onChartLongPressed(me: MotionEvent?) {}

        @RequiresApi(Build.VERSION_CODES.O)
        override fun onChartDoubleTapped(me: MotionEvent?) {
            showFileNameDialog(context, yawData, pitchData, rollData)
        }

        override fun onChartSingleTapped(me: MotionEvent?) {}
        override fun onChartFling(
            me1: MotionEvent?, me2: MotionEvent?, velocityX: Float, velocityY: Float
        ) {
        }

        override fun onChartScale(me: MotionEvent?, scaleX: Float, scaleY: Float) {}
        override fun onChartTranslate(me: MotionEvent?, dX: Float, dY: Float) {}
    }

    chart.invalidate()
    // Enable auto-scaling
    chart.isAutoScaleMinMaxEnabled = true

    AndroidView({ chart }, modifier = modifier.padding(16.dp).border(1.dp, Color.Gray))
}

这里调用的是一个AndroidView,这个是Compose中的一个组件,用来显示Android原生的View。

这里实现一个动作,双击图表,会弹出一个对话框,让用户输入文件名,然后导出数据。

@RequiresApi(Build.VERSION_CODES.O)
fun showFileNameDialog(
    context: Context, yawData: List<Entry>, pitchData: List<Entry>, rollData: List<Entry>
) {
    val editText = EditText(context).apply {
        setHint("Enter file name")
        // get date and time
        val currentDateTime = java.time.LocalDateTime.now()
        val formatter = java.time.format.DateTimeFormatter.ofPattern("yyyyMMddHHmmss")
        setText(currentDateTime.format(formatter))
    }
    val dialog = AlertDialog.Builder(context).setTitle("Enter file name").setView(editText)
        .setPositiveButton("Save") { _, _ ->
            val fileName = editText.text.toString()
            if (fileName.isNotEmpty()) {
                saveDataToCsv(context, fileName, yawData, pitchData, rollData)
            } else {
                Toast.makeText(context, "File name cannot be empty", Toast.LENGTH_SHORT).show()
            }
        }.setNegativeButton("Cancel", null).create()
    dialog.show()
}

fun saveDataToCsv(
    context: Context,
    fileName: String,
    yawData: List<Entry>,
    pitchData: List<Entry>,
    rollData: List<Entry>
) {
    val file = File(context.getExternalFilesDir(null), "${fileName.trim()}.csv")
    FileWriter(file).use { writer ->
        writer.append("Time,Yaw,Pitch,Roll\n")
        for (i in yawData.indices) {
            writer.append("${yawData[i].x},${yawData[i].y},${pitchData[i].y},${rollData[i].y}\n")
        }
    }
    Toast.makeText(context, "Data saved to ${file.absolutePath}", Toast.LENGTH_SHORT).show()
}

结论

导出的数据很容易用Matlab或者Python画出来。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

总的来说,这个过程非常丝滑,最终编译的apk文件大小不到10MB,非常适合用来搞一些无聊的事情。

  • 代码
  • 数据
  • apk不推荐下载

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

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

相关文章

SpringBoot3 快速启动框架

文章目录 1 SpringBoot3 介绍 1.1 SpringBoot3 简介1.2 快速入门1.3 入门总结 2 SpringBoot3 配置文件 2.1 统一配置管理概述2.2 属性配置文件使用2.3 YAML配置文件使用2.4 批量配置文件注入2.5 多环境配置和使用 3 SpringBoot 整合 springMVC 3.1 实现过程3.2 web相关配置3.3…

大型ERP系统GL(总账管理)模块需求分析

主要介绍了GL系统的需求分析&#xff0c;包括系统概述、功能描述、帐薄管理、报表管理、期末处理、财务报表以及凭证的快速输入方式、可用性设计、保存、自动审核和打印等方面的内容。系统概述部分介绍了系统的功能结构和模块流程图。 功能描述部分详细描述了系统的基础资料和业…

利用python将图片转换为pdf格式的多种方法,实现批量转换,内置模板代码,全网最全,超详细!!!

文章目录 前言1、img2pdf库的使用1.2 安装img2pdf库1.3 案例演示&#xff08;模板代码&#xff09; 2、Pillow库的使用2.1 pillow库的安装2.2 案例演示&#xff08;模板代码&#xff09; 3、PyMuPDF库的使用3.1 安装pymupdf库3.2 案例演示&#xff08;模板代码&#xff09;2.3 …

协议幻变者:DeviceNet转ModbusTCP网关开启机器手臂智能新纪元

技术背景DeviceNet是一种广泛应用于工业自动化领域的现场总线标准&#xff0c;它能够实现控制器与现场设备之间的高效通信&#xff0c;常用于连接各种传感器、执行器以及其他工业设备&#xff0c;如机器人、电机驱动器等&#xff0c;具有实时性强、可靠性高的特点。而ModbusTCP…

Linux 安装运行gatk的教程

1.下载安装 wget https://github.com/broadinstitute/gatk/releases/download/4.1.8.1/gatk-4.1.8.1.zip2.解压 unzip *.zip3.查看 gatk --help 如下显示表示安装成功&#xff1a; 注意&#xff1a;仅限在该包所在位置的路径下能使用

使用Xilinx PCIE XDMA框架读写访问DDR3内容

在 FPGA 开发中&#xff0c;使用 XDMA&#xff08;PCIe DMA&#xff09;是实现主机和 FPGA 之间数据传输的常见方法。xdma_rw.exe 是一个官方提供的命令行工具&#xff0c;用于与 FPGA 进行读写操作&#xff0c;支持从 PCIe 总线读取或写入数据。我利用xdma框架实现了DDR3内存的…

【论文笔记之 Mega-TTS2】Boosting Prompting Mechanisms For Zero-Shot Speech Synthesis

本文对 Ziyue Jiang 等人于 2024 年发表的论文进行简单地翻译。如有表述不当之处欢迎批评指正。欢迎任何形式的转载&#xff0c;但请务必注明出处。 论文链接&#xff1a;https://arxiv.org/pdf/2307.07218 目录 Abstract1. 介绍2. 背景3. 方法3.1. 解耦出韵律和音色3.2. 压缩…

haproxy+nginx负载均衡实验

准备三台虚拟机&#xff1a; HAProxy 服务器192.168.65.131Web 服务器 1192.168.65.132Web 服务器 2192.168.65.133 在 HAProxy 服务器&#xff08;192.168.65.131&#xff09;上操作&#xff1a; 安装 HAProxy&#xff1a; sudo yum install -y haproxy编辑 HAProxy 配置…

[论文阅读] (34)ESWA2024 基于SGDC的轻量级入侵检测系统

《娜璋带你读论文》系列主要是督促自己阅读优秀论文及听取学术讲座&#xff0c;并分享给大家&#xff0c;希望您喜欢。由于作者的英文水平和学术能力不高&#xff0c;需要不断提升&#xff0c;所以还请大家批评指正&#xff0c;非常欢迎大家给我留言评论&#xff0c;学术路上期…

华三交换机怎么进行链路聚合?

目录 准备&#xff1a;两台交换机 配置&#xff1a;进行交换机配置 完成&#xff1a;检查链路聚合是否成功 准备&#xff1a;两台交换机 1、进行连线 2、上行Core交换机都选取 Ten-GigabitEthernet1/0/51、Ten-GigabitEthernet1/0/52和 Ten-GigabitEthernet2/0/51、Ten-Giga…

【无线传感网】无线传感器网络覆盖技术

文章目录 覆盖算法设计思路及性能评价标准覆盖感知模型布尔感知模型概率感知模型 无线传感网络覆盖算法分类按照配置方式确定性覆盖随机性覆盖 根据覆盖目标面覆盖点覆盖栅栏覆盖 典型的WSN覆盖算法与协议基于网格的覆盖定位传感器配置算法圆周覆盖连通传感器覆盖轮换活跃/休眠…

积分图(Integral Image)与均值滤波的快速实现

积分图&#xff08;Integral Image&#xff09;也称为求和图&#xff08;Summed Area Table&#xff09;&#xff0c;是一种用于快速计算图像中任意矩形区域像素值总和的技术。 基本概念 积分图的每个位置(i, j)存储的是从图像左上角(1, 1)到当前位置(i, j)所有像素值的累积和…

MySQL数据库笔记——多版本并发控制MVCC

大家好&#xff0c;这里是Good Note&#xff0c;关注 公主号&#xff1a;Goodnote&#xff0c;本文详细介绍MySQL的并发控制&#xff1a;多版本并发控制MVCC。 文章目录 背景介绍数据库并发控制——锁机制悲观锁和乐观锁悲观锁乐观锁 数据库并发控制——MVCC 的引入MVCC 和锁机…

css3实现文字下滑波浪线

上效果 上菜 text-decoration 属性作用&#xff1a;用于设置或检索文本的装饰线&#xff0c;如下划线、上划线、删除线等 text-decoration: line || color || style; 参数&#xff1a; line: 指定装饰线类型&#xff0c;如 underline&#xff08;下划线&#xff09;、overline&…

Springboot 3项目整合Knife4j接口文档(接口分组详细教程)

文章目录 前言一、Spring Boot 3.0整合Knife4j二、OpenApi 3注解的使用规范三、使用步骤 1.Spring Boot 3.0项目中使用knife4j2.在application.yml中添加knife4j相关配置3.设置WebMvc相关配置&#xff08;解决封装统一异常处理后doc.html无法打开的问题&#xff09;4.创建Knif…

2024年中国新能源汽车用车发展怎么样 PaperGPT(一)

概述 在国家政策的强力扶持下&#xff0c;2024年中国新能源汽车市场迎来了新的发展机遇。本文将基于《中国新能源汽车用车报告&#xff08;2024年&#xff09;》的数据&#xff0c;对新能源汽车的市场发展和用车趋势概述。 新能源汽车市场发展 政策推动&#xff1a;国家和地…

华三交换机如何进行堆叠?

准备&#xff1a;两台交换机堆叠 1、进行连线 2、交换机都选取 FortyGigE1/0/53 和 FortyGigE1/0/54 做 堆叠口 配置&#xff1a;进行交换机配置 X_T1_Core_1&#xff1a; [X_T1_Core_1]irf domain 0 //同一拓扑内如果有其它堆叠组&#xff0c;domain不能重复 [X_T1_Core_1]…

活动预告 | Microsoft 安全在线技术公开课:通过扩展检测和响应抵御威胁

课程介绍 通过 Microsoft Learn 免费参加 Microsoft 安全在线技术公开课&#xff0c;掌握创造新机遇所需的技能&#xff0c;加快对 Microsoft Cloud 技术的了解。参加我们举办的“通过扩展检测和响应抵御威胁”技术公开课活动&#xff0c;了解如何更好地在 Microsoft 365 Defen…

Sonic:开源Go语言开发的高性能博客平台

Sonic&#xff1a;一个用Go语言开发的高性能博客平台 简介 Sonic&#xff0c;一个以其速度如声速般快速而命名的博客平台&#xff0c;是一个用Go语言开发的高性能博客系统。正如其名字所暗示的&#xff0c;Sonic旨在提供一个简单而强大的博客解决方案。这个项目受到了Halo项目…

大模型WebUI:Gradio全解系列8——Additional Features:补充特性(上)

大模型WebUI&#xff1a;Gradio全解系列8——Additional Features&#xff1a;补充特性&#xff08;上&#xff09; 前言本篇摘要8. Additional Features&#xff1a;补充特性8.1 队列8.1.1 使用方法8.1.2 配置队列演示 8.2 输入输出流8.2.1 输出流1. 生成器yield2. 流媒体 8.2…