Mediapipe框架(二)人脸检测

Mediapipe框架(二)人脸检测

  • MediaPipe 是一款由 Google Research 开发并开源的多媒体机器学习模型应用框架。谷歌的一系列重要产品,如Google Lens、ARCore、Google Home等都已深度整合了 MediaPipe。

  • MediaPipe目前支持的解决方案(Solution)及支持的平台如下图所示,除了视觉任务,还支持文本、语音及生成式AI任务(实验中)。作为一款跨平台框架,MediaPipe 不仅可以被部署在Web端,更可以在多个移动端 (Android和苹果 iOS)和嵌入式平台(Google Coral 和树莓派)中作为设备端机器学习推理框架。

  • MediaPipe的每个解决方案(Solution)包括一个或多个模型,一些解决方案还可以自定义模型(使用Model Maker)。

  • 今天,我们主要来了解下人脸检测(Face Detection)

在这里插入图片描述

官网地址(需魔法流量): MediaPipe | Google for Developers

Mediapipe框架的核心概念可以参考:Mediapipe框架(一)人手关键点检测

1 MediaPipe人脸检测模型概述

1.1 概述

  • MediaPipe人脸检测是一种超快速的人脸检测解决方案,除了提供面部边界框外,还可以输出6个特征点的坐标:双眼中央、耳垂、嘴部中央与鼻尖

  • MediaPipe人脸检测所用模型是BlazeFace的变体,BlazeFace 是谷歌19年提出的一种针对移动 GPU 推断进行优化的轻量级且精确的人脸检测器。

    • BlazeFace 使用类似 MobileNetV1/V2 的轻量级特征提取网络。
    • BlazeFace 在旗舰移动设备上以200-1000 + FPS的速度运行。 这种超实时性能使其能够应用于任何对性能要求极高的增强现实应用中。
    • 作者在MobileNet-SSD目标检测框架下,改进了网络结构、anchor机制、替换NMS后处理,使算法在人脸检测任务中保持高精度的同时,在移动GPU上速度还很快。
  • 该检测器的超实时性能使其可应用于其他需要准确地关注面部区域的模型:

例如
1、3D面部关键点检测
2、面部特征或表情分类,以及面部区域分割等

  • 在midiapipe官网中,提到了三个BlazeFace相关模型。
    • BlazeFace (short-range),目前只有此模型提供了预训练权重。一种轻量级模型,用于在智能手机相机或网络摄像头的自拍式图像中检测单个或多个面部。该模型针对短距离的前置手机摄像头图像进行了优化。模型架构使用了一种带有自定义编码器的Single Shot Detector (SSD)卷积网络技术。
    • BlazeFace (full-range)。这是一个相对轻量级的模型,用于在智能手机摄像头或网络摄像头拍摄的图像中检测单个或多个面部。该模型针对全范围图像进行了优化,比如使用后置手机摄像头拍摄的图像。模型架构使用了类似于CenterNet卷积网络的技术,并配备了自定义编码器。
    • BlazeFace Sparse (full-range)。这是BlazeFace(full-range)模型的轻量级版本,大小大约缩小了60%。

在这里插入图片描述

1.2 BlazeFace模型概述

BlazeFace已经被谷歌用于实际的工程中,因此这篇文章非常值得参考。

论文地址:https://arxiv.org/abs/1907.05047

BlazeFace是Google专为移动端GPU定制的人脸检测方案。主要创新点如下:

  1. 专为轻量级检测定制的紧凑型特征提取网络,类似于MobielNet。
  2. 相比SSD模型,对GPU更友好的anchor机制。
  3. 采用“tie resolution strategy”而非NMS处理重叠的预测结果。

1.2.1 BlazeBlock模块

  • 我们知道深度可分离卷积是轻量网络经常采用的卷积形式。它使用两步卷积运算,即Depthwise卷积与Pointwise卷积替代常规的单次卷积。

  • 作者在iPhone上实测后发现,一个56×56×128大小、16-bit张量的Depthwise卷积运算耗时0.07ms,而伴随其后128-128通道的Pointwise卷积运算耗时0.3ms,是前者的4倍以上(作者认为是内存存取的固定开销造成)。因此作者提出在Depthwise卷积中使用较大的卷积核(即使用5*5卷积核代替3*3卷积核,扩大感受野),这样只需要较少的bottleneck数量就可以获得指定大小的感受野范围。作者将该结构命名为 BlazeBlock

  • 另外为了促进感受野Size的传递,提出了double BlazeBlock 模块。该模块在bottleneck的最前端又塞入了一层深度可分离卷积,并增加了Max Pooling与Channel Padding作为可选旁路。

在这里插入图片描述

1.2.2 特征提取

  • 对于前置摄像头这种人脸占图像很大面积、人脸尺度变化较小的场景,作者定义了更加轻量级的特征提取。

  • 作者使用了128×128像素的RGB输入,后跟1个常规5×5卷积、5个BlazeBlock、6个Double BlazeBlock,其中通道数最大值为96(网络结构可以参考原文)。

  • BlazeFace最低的空间分辨率为8×8(SSD分辨率一直降低到1×1)。

1.2.3 Anchor机制

  • BlazeFace的特征提取网络在特征图缩小到8×8时就结束了。作者给出的理由是“Pooling Pyramid Network (PPN) 表明特征图在达到特定分辨率后的附加运算存在冗余”。
  • 另外作者指出:在GPU上调度特定运算层存在可察觉的固定损耗,尤其是针对CPU优化的深度低分辨率网络层。
  • 综合以上两点,BlazeFace网络的深度止步在8×8大小,但是将anchor的数量扩增到了6个。同时考虑人脸相对固定的形状变化,将anchor的比例固定在1:1。

在这里插入图片描述

1.2.4 后处理

  • 由于8×8特征图上扩增了anchor数量,大量的检测结果会重叠在一起。传统的处理方式是使用NMS。在SSD的NMS中,只有一个胜出的anchor用于算法输出,这导致在视频中进行检测时,人脸框抖动明显。
  • 为了降低这种效应,作者提出使用融合(blending)而非抑制(suppression)策略。具体的做法为将重叠边界框回归参数做加权平均计算,而NMS则是采用重叠结果中的一个。
  • 作者称对于在视频中的面部检测任务,此调整导致准确度提高10%。

1.2.5 实验结果

该文重点说明在手机终端真实应用中,检测算法的加速,只是在谷歌的私有数据集上与MobileNetV2-SSD的比较。

  • 下图是比较结果,精度高于MobileNetV2-SSD,在iPhone XS上的速度也从2.1毫秒降到0.6毫秒。

在这里插入图片描述

  • 更多手机上的运算速度比较,BlazeFace在不同的手机上都获得了成倍的速度提升:

在这里插入图片描述

2 Mediapipe人脸检测代码实现

  • 该任务配置项如下图所示:
配置项描述取值范围默认值
running_mode设置任务的运行模式。有三种模式:IMAGE:用于单张图像输入。VIDEO:用于视频解码帧。LIVE_STREAM:用于输入数据的实时流模式,例如来自摄像机的数据。在此模式下,必须调用resultListener来设置一个监听器以异步接收结果。{IMAGE, VIDEO, LIVE_STREAM}IMAGE
min_detection_confidence脸部检测被视为成功所需的最低置信度分数。Float [0,1]0.5
min_suppression_threshold非极大值抑制阈值Float [0,1]0.3
result_callback异步回调结果(仅用于LIVE_STREAM模式)N/ANot set

1、安装mediapipe包

pip install mediapipe

2、下载预训练模型:

下载地址:

https://storage.googleapis.com/mediapipe-models/face_detector/blaze_face_short_range/float16/1/blaze_face_short_range.tflite

3、检测结果

  • 人脸检测器返回一个FaceDetectorResult对象来表示每次检测运行的结果。该结果对象包含检测到的人脸的边界框和每个检测到的人脸的置信度分数等信息。

  • 检测到的面部的集合,其中包含边界框和6个关键点。边界框未标准化,不过每个关键点(由x和y组成)通过图像的宽度和高度进行了标准化。

    以下是此任务输出数据的示例。

FaceDetectionResult:
  Detections:
    Detection #0:
      BoundingBox:                  #人脸的bbox 
        origin_x: 126
        origin_y: 100
        width: 463
        height: 463
      Categories:
        Category #0:   
          index: 0
          score: 0.9729152917861938  # 人脸检测的置信度
      NormalizedKeypoints:           # 每张人脸所含的6个关键点
        NormalizedKeypoint #0:
          x: 0.18298381567001343
          y: 0.2961040139198303
        NormalizedKeypoint #1:
          x: 0.3302789330482483
          y: 0.29289937019348145
        ... (6 keypoints for each face)
    Detection #1:
      BoundingBox:
        origin_x: 616
        origin_y: 193
        width: 430
        height: 430
      Categories:
        Category #0:
          index: 0
          score: 0.9251380562782288
      NormalizedKeypoints:
        NormalizedKeypoint #0:
          x: 0.6151331663131714
          y: 0.3713381886482239
        NormalizedKeypoint #1:
          x: 0.7460576295852661
          y: 0.38825345039367676
        ... (6 keypoints for each face)

4、检测速度

  • 下图为该模型的推理时间,在CPU仅花费2.94ms。

在这里插入图片描述

2.1 单张图像的人脸检测

  • 需要下载预训练模型
  • utils代码在mediapipe框架小案例 中
import numpy as np
import mediapipe as mp
from mediapipe.tasks import python
from mediapipe.tasks.python import vision
import cv2
from utils import visualize


def detect_face_from_image(img_path):
    # 1、创建人脸检测器
    # 需要先下载预训练模型
    base_options = python.BaseOptions(model_asset_path='blaze_face_short_range.tflite')
    options = vision.FaceDetectorOptions(base_options=base_options)
    detector = vision.FaceDetector.create_from_options(options)

    # 2、加载输入图片
    image = mp.Image.create_from_file(img_path)


    # 3、使用下载好的模型进行人脸检测
    detection_result = detector.detect(image)
    print(detection_result)

    # 4、 可视化
    image_copy = np.copy(image.numpy_view())
    annotated_image = visualize(image_copy, detection_result)
    rgb_annotated_image = cv2.cvtColor(annotated_image, cv2.COLOR_BGR2RGB)
    # 在使用OpenCV的cv2.imshow函数显示图像时,它会默认将传入的图像数据解释为BGR格式
    # 如果你传入的是RGB格式的图像数据,OpenCV会在显示时进行颜色通道的调整,使图像以BGR格式进行显示。
    cv2.imshow('face detection', rgb_annotated_image)

    # 输入esc结束捕获
    if cv2.waitKey(0) == 27:
        cv2.destroyAllWindows()


if __name__ == '__main__':
    detect_face_from_image(img_path='face_image.jpg')

检测结果如下:

在这里插入图片描述

2.2 视频帧的人脸检测

  • 这里用pyside6做了一个简单检测页面
  • 可以调用本地电脑摄像头进行在线检测,也可进行离线检测
  • 需要设置running_mode为VisionRunningMode.VIDEO
import time
from utils import visualize
import mediapipe as mp
from mediapipe.tasks import python
from mediapipe.tasks.python import vision
import cv2


from PySide6 import QtWidgets, QtCore, QtGui
from threading import Thread

# 通过pyside6实现一个简单的界面
class MWindow(QtWidgets.QMainWindow):
    def __init__(self):

        super().__init__()
        # 设置界面
        self.setupUI()
        # 给按钮绑定事件
        self.videoBtn.clicked.connect(self.startVideo)
        self.camBtn.clicked.connect(self.startCamera)
        self.stopBtn.clicked.connect(self.stop)

        # 定义定时器,用于控制显示视频的帧率
        self.timer_camera = QtCore.QTimer()
        # 定时到了,回调 self.show_image
        self.timer_camera.timeout.connect(self.show_image)

        # 初始化模型
        self.initialize_model()

        # 要处理的视频帧图片队列,目前就放1帧图片
        self.frameToAnalyze = []
        self.frame_timestamp_ms = int(time.time())

        # 启动处理视频帧独立线程(设置未守护线程)
        Thread(target=self.frameAnalyzeThreadFunc, daemon=True).start()


    def initialize_model(self):
        # 创建人脸检测器,注意running_mode设置为VIDEO模式
        VisionRunningMode = mp.tasks.vision.RunningMode
        base_options = python.BaseOptions(model_asset_path='blaze_face_short_range.tflite')
        options = vision.FaceDetectorOptions(base_options=base_options, running_mode=VisionRunningMode.VIDEO)
        self.detector = vision.FaceDetector.create_from_options(options)


    def setupUI(self):
        self.resize(1200, 800)
        self.setWindowTitle('人脸检测系统')
        # 设置窗口在屏幕中间
        availableGeometry = QtGui.QGuiApplication.primaryScreen().availableGeometry()
        window = self.frameGeometry()
        window.moveCenter(availableGeometry.center())
        self.move(window.topLeft())

        # central Widget
        centralWidget = QtWidgets.QWidget(self)
        self.setCentralWidget(centralWidget)

        # central Widget 里面的 主 layout
        mainLayout = QtWidgets.QVBoxLayout(centralWidget)

        # 界面的上半部分 : 图形展示部分
        topLayout = QtWidgets.QHBoxLayout()
        self.label_ori_video = QtWidgets.QLabel(self)
        self.label_treated = QtWidgets.QLabel(self)
        self.label_ori_video.setMinimumSize(520, 400)
        self.label_treated.setMinimumSize(520, 400)
        self.label_ori_video.setStyleSheet('border:1px solid #D7E2F9;')
        self.label_treated.setStyleSheet('border:1px solid #D7E2F9;')

        topLayout.addWidget(self.label_ori_video)
        topLayout.addWidget(self.label_treated)
        mainLayout.addLayout(topLayout)

        # 界面下半部分: 输出框 和 按钮
        groupBox = QtWidgets.QGroupBox(self)
        bottomLayout =  QtWidgets.QHBoxLayout(groupBox)
        self.textLog = QtWidgets.QTextBrowser()
        bottomLayout.addWidget(self.textLog)
        mainLayout.addWidget(groupBox)

        btnLayout = QtWidgets.QVBoxLayout()
        self.videoBtn = QtWidgets.QPushButton('🎞️视频文件')
        self.camBtn   = QtWidgets.QPushButton('📹摄像头')
        self.stopBtn  = QtWidgets.QPushButton('🛑停止')
        btnLayout.addWidget(self.videoBtn)
        btnLayout.addWidget(self.camBtn)
        btnLayout.addWidget(self.stopBtn)
        bottomLayout.addLayout(btnLayout)


    def startVideo(self):
        # 选择检测的视频路径
        options = QtWidgets.QFileDialog.Options()
        filePath, _ = QtWidgets.QFileDialog.getOpenFileName(self, "Select Video File", "", "Video Files (*.mp4 *.avi)",
                                                  options=options)
        if filePath:
            self.textLog.clear()
            self.textLog.append('目前检测视频的路径:' + filePath)


        self.cap = cv2.VideoCapture(filePath)
        if not self.cap.isOpened():
            print("startVideo中摄像头不能打开")
            return

        if self.timer_camera.isActive() == False:  # 若定时器未启动
            self.timer_camera.start(25)
            print('startVideo 中开启了定时器')

    # 摄像头按钮绑定的事件
    def startCamera(self):
        # 在 windows上指定使用 cv2.CAP_DSHOW 会让打开摄像头快很多,
        # 在 Linux/Mac上 指定 V4L, FFMPEG 或者 GSTREAMER
        self.cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)
        if not self.cap.isOpened():
            print("startCamera中摄像头不能打开")
            return

        self.textLog.clear()
        self.textLog.append('当前正在使用摄像头进行人脸检测')

        if self.timer_camera.isActive() == False:  # 若定时器未启动
            self.timer_camera.start(25)

    # 定时读取图像,并进行处理
    def show_image(self):
        ret, frame = self.cap.read()  # 从视频流中读取
        if not ret:
            return
        # 把读到的16:10帧的大小重新设置
        frame = cv2.resize(frame, (520, 400))
        # 视频色彩转换回RGB,OpenCV images as BGR
        frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
        qImage = QtGui.QImage(frame.data, frame.shape[1], frame.shape[0],
                                 QtGui.QImage.Format.Format_RGB888)  # 变成QImage形式
        # 往显示视频的Label里 显示QImage
        self.label_ori_video.setPixmap(QtGui.QPixmap.fromImage(qImage))

        # 如果当前没有处理任务
        if len(self.frameToAnalyze) < 1:
            self.frameToAnalyze.append(frame)

    # 单独线程进行人脸检测
    def frameAnalyzeThreadFunc(self):
        while True:
            if not self.frameToAnalyze:
                time.sleep(0.01)
                continue
            numpy_frame_from_opencv = self.frameToAnalyze.pop(0)
            mp_image = mp.Image(image_format=mp.ImageFormat.SRGB, data=numpy_frame_from_opencv)
            # 使用人脸检测器检测单张图像
            detection_result = self.detector.detect_for_video(mp_image, self.frame_timestamp_ms)

            # 可视化人脸bbox及6个关键点
            annotated_image = visualize(numpy_frame_from_opencv, detection_result)
            img = cv2.cvtColor(annotated_image, cv2.COLOR_RGB2BGR)
            # 变成QImage形式
            qImage = QtGui.QImage(img.data, img.shape[1], img.shape[0],
                                    QtGui.QImage.Format.Format_BGR888)
            # 往显示Label里 显示QImage
            self.label_treated.setPixmap(QtGui.QPixmap.fromImage(qImage))
            time.sleep(0.005)
            self.frame_timestamp_ms += 500

    # 停止按钮绑定的事件
    def stop(self):
        self.timer_camera.stop()      # 关闭定时器
        self.cap.release()            # 释放视频流
        self.label_ori_video.clear()  # 清空视频显示区域
        self.label_treated.clear()    # 清空视频显示区域
        print('关闭人脸检测系统')


if __name__ == '__main__':
    app = QtWidgets.QApplication()
    window = MWindow()
    window.show()
    app.exec()

检测结果如下:

在这里插入图片描述

2.3 实时流的人脸检测

  • 需要设置running_mode为VisionRunningMode.LIVE_STREAM
  • 这里实现一个回调函数result_callback,用来将检测结果输入到output.avi文件中
  • 检测是异步检测detector.detect_async()
import mediapipe as mp
from mediapipe.tasks import python
from mediapipe.tasks.python import vision
import cv2
import time
from utils import visualize


# 创建一个视频编解码器
fourcc = cv2.VideoWriter_fourcc(*'XVID')
out = cv2.VideoWriter(filename='output.avi', fourcc=fourcc, fps=20.0, frameSize=(640, 480))

# 回调函数,将检测的结果保存为视频
FaceDetectorResult = mp.tasks.vision.FaceDetectorResult
def print_result(result: FaceDetectorResult, output_image: mp.Image, timestamp_ms: int):
    if cap.isOpened():
        annotated_image = visualize(img, result)
        out.write(annotated_image)


# 创建人脸检测器
VisionRunningMode = mp.tasks.vision.RunningMode
base_options = python.BaseOptions(model_asset_path='blaze_face_short_range.tflite')
options = vision.FaceDetectorOptions(base_options=base_options
                                        , running_mode = VisionRunningMode.LIVE_STREAM
                                        , result_callback = print_result
                                     )
detector = vision.FaceDetector.create_from_options(options)

cap = cv2.VideoCapture(0)
frame_timestamp_ms = int(time.time())

while True:
    if cap.isOpened():
        # 从相机从捕获一帧图片
        _, img = cap.read()
        cv2.imshow('original', img)
        # 将图像从BGR颜色空间转换为RGB颜色空间
        numpy_frame_from_opencv = cv2.cvtColor(img, cv2.COLOR_BGRA2RGB)
        mp_image = mp.Image(image_format=mp.ImageFormat.SRGB, data=numpy_frame_from_opencv)
        # 异步检测
        detector.detect_async(mp_image, int(frame_timestamp_ms))
        # 输入esc结束捕获
        if cv2.waitKey(25) == 27:
            print('exited')
            break
        frame_timestamp_ms += 25

cap.release()
out.release()
cv2.destroyAllWindows()

上文所用的检测模型、图片及视频素材也已经上传到仓库。

参考博客连接:

人脸检测方案BlazeFace解读

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

得物面试:10wqps高并发,如何防止重复下单?

尼恩说在前面 在40岁老架构师 尼恩的读者交流群(50)中&#xff0c;最近有小伙伴拿到了一线互联网企业如得物、阿里、滴滴、极兔、有赞、希音、百度、网易、美团的面试资格&#xff0c;遇到很多很重要的面试题&#xff1a; 10wqps高并发&#xff0c;如何防止重复提交/支付订单&…

基于springboot+vue+微信小程序的医院预约挂号系统(前后端分离)(含参考论文)

基于springbootvue微信小程序的医院预约挂号系统(前后端分离)(含参考论文) 前言 本系统适用于毕业设计、课程设计或者学习等&#xff0c;适合选题&#xff1a;医院预约挂号、微信小程序、前后端分离等。系统采用springbootvue整合开发&#xff0c;前端框架主要使用了element-…

半山腰总是挤的,你得去山顶看看

如果你去爬山&#xff0c;你会发现&#xff0c;半山腰的人总是最多的&#xff0c;越往上走&#xff0c;人越少&#xff0c;而最好的风景你只能到山顶去看。所以如果你想要欣赏到最好的风景&#xff0c;往往付出的努力也最多。爬山不能走捷径&#xff0c;只能你一步一个脚印走上…

块设备的读写框架

生成块设备 我们以虚拟文件的接口&#xff0c;来看这个框架&#xff1b;因为这是从从应用层到内核的必经之路&#xff1b;使用vfs_mknod来生成块设备文件&#xff0c;并初始化fops mknoddo_mknodatvfs_mknodshmem_mknodshmem_get_inodeinit_special_inode void init_special_…

SV学习笔记(三)

类和对象概述 类和对象 面向对象的编程语言更符号人对自然语言的理解&#xff08;属性property和功能function&#xff09;。 这个世界由无数的类&#xff08;class&#xff09;和对象&#xff08;object&#xff09;构成的。 类是将相同的个体抽象出来的描述方式&#xff0c…

【Servlet】thymeleaf快速入门

文章目录 一、thymeleaf介绍二、入门案例 一、thymeleaf介绍 Thymeleaf&#xff1a;视图模板技术 在index.html页面上加载java内存中的fruitList数据&#xff0c;这个过程我们称之为渲染&#xff08;render&#xff09;。 thymeleaf是用来帮助我们做视图渲染的一个技术。 二…

Python学习从0到1 day20 第二阶段 面向对象 ③ 继承

循此苦旅&#xff0c;以达天际 —— 24.4.3 一、继承的基础语法 学习目标&#xff1a; ① 理解继承的概念 ② 掌握继承的使用方式 ③ 掌握pass关键字的作用 单继承 语法&#xff1a; class 类名(父类名): 类内容体 继承分为&#xff1a;单继承和多继承 继承表示&#xff1a;将从…

redis---HyperLogLog

HyperLogLog是一个基数统计的算法&#xff0c;如果集合中的每个元素都是唯一且不重复的&#xff0c;那么这个集合的基数就是集合中元素的个数 它的原理是使用随机算法来计算&#xff0c;通过牺牲一定的精确度&#xff0c;来换取更小的内存消耗&#xff0c;优点就是占用内存小。…

“帮助“Java成长的世界级大师不简单!

文章目录 初探编程&#xff1a;“天啊&#xff0c;真酷&#xff0c;程序真的能学习。”哺育Java成长&#xff0c;成为Java幕后英雄出书《Effective Java》斩获Jolt图书大奖 是谁&#xff1f;作品一出版就获得著名的Jolt图书大奖&#xff0c;每一版本豆瓣评分均超9.0&#xff01…

[已解决] slam_gmapping: undefined symbol: _ZN8GMapping14sampleGaussianEdm问题

之前用的好好的gampping建图功能包&#xff0c;今天突然不能用了&#xff0c;运行报错如下&#xff1a; /opt/ros/noetic/lib/gmapping/slam_gmapping: symbol lookup error: /opt/ros/noetic/lib/gmapping/slam_gmapping: undefined symbol: _ZN8GMapping14sampleGaussianEdm …

ShardingJdbc兼容达梦

ShardingJdbc兼容达梦 ​ 本章详细说ShardingJdbc和达梦数据库的扩展和配置问题&#xff0c;ShardingJdbc和DruidDataSource、Mybatis整合的兼容、冲突问题&#xff0c;以及这些问题的解决方案。&#xff0c;干货满满&#xff0c;全网独一份&#xff0c;建议收藏。本章不说Sha…

数码论坛系统的设计与实现|Springboot+ Mysql+Java+ B/S结构(可运行源码+数据库+设计文档)电子科技数码爱好者交流信息新闻畅聊讨论评价

本项目包含可运行源码数据库LW&#xff0c;文末可获取本项目的所有资料。 推荐阅读300套最新项目持续更新中..... 最新ssmjava项目文档视频演示可运行源码分享 最新jspjava项目文档视频演示可运行源码分享 最新Spring Boot项目文档视频演示可运行源码分享 2024年56套包含ja…

680.验证回文串II-力扣

680.验证回文串II-力扣 给你一个字符串 s&#xff0c;最多可以从中删除一个字符。 请你判断 s 是否能成为回文字符串&#xff1a;如果能&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false。 示例1&#xff1a; 输入&#xff1a;s “aba” 输出&#xff1a;true示…

Python就业前景如何?薪资待遇怎么样?

前言 Python作为一种高级编程语言&#xff0c;已经在多个领域得到了广泛的应用&#xff0c;包括数据分析、人工智能、Web开发等。随着技术的不断发展和应用领域的不断扩展&#xff0c;Python的就业前景也越来越广阔。 首先&#xff0c;Python在数据分析领域的应用非常广泛。随…

mac | Windows 本地部署 Seata2.0.0,Nacos 作为配置中心、注册中心,MySQL 存储信息

1、本人环境介绍 系统 macOS sonama 14.1.1 MySQL 8.2.0 &#xff08;官方默认是5.7版本&#xff09; Seata 2.0.0 Nacos 2.2.3 2、下载&数据库初始化 默认你已经有 Nacos、MySQL&#xff0c;如果没有 Nacos 请参考我的文章 &#xff1a; Docker 部署 Nacos&#xff08;单机…

滴滴盈利,司机“受伤”

近日&#xff0c;滴滴对外披露了2023年Q4及全年业绩。 财报数据显示&#xff0c;2023年Q4&#xff0c;滴滴实现营收494亿元&#xff0c;同比增长55.4%&#xff0c;净利润达11亿元&#xff1b;2023年全年滴滴实现营收共计1924亿元&#xff0c;同比增长36.6%&#xff0c;净利润达…

springboot对接minio的webhook全过程

前言 近日需要将minio的apache2.0版本给用起来&#xff0c;顺便要完善一下原有的文件上传管理系统&#xff0c;其中很重要的一点是&#xff0c;在原有客户端直传的基础上&#xff0c;再添加 minio 的上传回调给服务端做后续处理。 本文重点在于&#xff0c;介绍整个minio与spr…

MySQL生产环境常见故障及解决方案汇总

MySQL生产环境常见故障及解决方案汇总 1. MySQL主从同步异常故障1.1. 情景说明1.2. 排查过程1.3. 数据同步2. MySQL慢查询故障1. MySQL主从同步异常故障 1.1. 情景说明 MySQL主库网卡需要更换IP地址,并将原IP地址配置为MySQL集群的VIP地址,上层应用程序其实不需要更改连接My…

VUE——生命周期

概念&#xff1a; mounted:挂载 new Vue({el: "#x",data: {},methods: {},mounted() {}, }) 系统会自己调用&#xff0c;不需要我们调用。 案例 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><…

JavaScript(五)---【DOM】

零.前言 JavaScript(一)---【js的两种导入方式、全局作用域、函数作用域、块作用域】-CSDN博客 JavaScript(二)---【js数组、js对象、this指针】-CSDN博客 JavaScript(三)---【this指针&#xff0c;函数定义、Call、Apply、函数绑定、闭包】-CSDN博客 JavaScript(四)---【执…