UVC 设备框架在 Linux 4.15 内核的演变

1. 概述

发现之前的uvc框架和现在的还是有一些差别的(比如从videobuf 过渡到videobuf2),写个blog记录一下,方便以后查询,我的内核版本:Linux 4.15

  • UVC(USB Video Class)设备框架是建立在V4L2(Video4Linux version 2)子系统之上的。UVC框架主要负责管理通过USB接口连接的视频捕获设备,如网络摄像头。

2. 流程分析

  • 打开设备文件 : 应用程序通过文件I/O打开设备时,内核创建一个uvc_fh 文件句柄实例,将其关联到特定的视频流
  • 缓冲区队列操作:uvc_fh通过它的视频流uvc_streaming 结构体间接操作vb2_queue ,以执行如缓冲区排队(qbuf),和缓冲区准备(reqbufs)等操作
  • 数据流传输: 视频数据通过 vb2_buffer 结构体在用户空间和UVC硬件之间传输

3. 主要的内核结构体

图1
可以看出当前版本下的UVC驱动和V4L2之间的交互非常密切。V4L2是Linux内核中用于处理视频捕获和输出设备的一个标准API框架,而UVC驱动则是V4L2框架下用于支持符合USB视频类规范的摄像头和其他视频设备的一个具体实现

3.1 UVC驱动和V4L2之间主要的交互方式:

  1. 设备注册和初始化
    UVC驱动在系统启动或USB视频设备插入时,会被初始化并注册为V4L2设备。这一过程包括设置设备的V4L2能力、支持的格式、控制操作等,确保UVC设备能够通过V4L2接口与用户空间应用程序交互。
  2. 控制查询和设置
    UVC驱动实现了一系列V4L2控制类(control class)接口,允许用户空间应用程序查询和设置视频设备的参数,如亮度、对比度、饱和度等。这些控制操作通过UVC驱动转换为USB传输,与硬件设备交互。
  3. 缓冲区管理
    UVC驱动使用V4L2提供的videobuf2 API来管理视频帧的缓冲区。这包括缓冲区的分配、队列管理、数据传输等。Videobuf2作为V4L2的一部分,提供了一个高效的机制来处理视频数据的缓冲和流转。
  4. 数据流控制
    用户空间应用程序可以通过V4L2接口来启动和停止视频流。UVC驱动响应这些请求,通过USB接口与硬件设备进行交互,控制视频数据的捕获和传输。
  5. 事件处理
    UVC驱动能够处理来自硬件的事件,比如状态变化、错误报告等,并通过V4L2框架将这些事件上报给用户空间应用程序,使得应用程序能够对特定的硬件事件做出响应。
  6. 格式协商
    在视频捕获或输出过程中,UVC驱动和用户空间应用程序会通过V4L2接口进行格式协商,确定视频数据的格式、分辨率、帧率等参数。UVC驱动根据这些协商结果配置USB视频设备,以满足应用程序的需求。

4. UVC_Driver 驱动入口出口函数

static int __init uvc_init(void)
{
...ret = usb_register(&uvc_driver.driver);return 0;
...
}static void __exit uvc_cleanup(void)
{usb_deregister(&uvc_driver.driver);
}
module_init(uvc_init);
module_exit(uvc_cleanup);

接下里就是关于uvc_driver 结构体

struct uvc_driver uvc_driver = {.driver = {.name		= "uvcvideo",.probe		= uvc_probe,.disconnect	= uvc_disconnect,.suspend	= uvc_suspend,.resume		= uvc_resume,.reset_resume	= uvc_reset_resume,.id_table	= uvc_ids,.supports_autosuspend = 1,},
};

4.1 uvc_probe

这里主要是分配 设置 设置结构体,以及一些其他的操作

4.1.1 分配 uvc_device

if ((dev = kzalloc(sizeof *dev, GFP_KERNEL)) == NULL)INIT_LIST_HEAD(&dev->entities); INIT_LIST_HEAD(&dev->chains);  INIT_LIST_HEAD(&dev->streams); 
  • entities: 指的也就是摄像头、输入终端(ITT)、输出终端(OTT)、处理单元(PU)和扩展单元(XU)等。每个实体代表设备的一个功能部分,可能是视频捕获的源头(例如摄像头),或是对视频数据进行处理的模块(例如编码器)。
  • chains: 视频链是由一系列实体(entities)连接而成的路径,从视频捕获的源头开始,经过一系列处理,最终到达输出。每个视频链代表了一种特定的视频流处理流程
  • streams :视频流可以理解为通过一个特定视频链(chains)实现的数据流,包含实际的视频帧数据
    在这里插入图片描述

解析设备接口描述符 并将信息储存到 uvc_device *dev; 以便后面使用

uvc_parse_control(dev)

4.1.2 分配 uvc_device注册 video 设备节点

  1. 注册uvc设备到V4L2框架下, 将V4L2设备结构体 v4l2_device 与内核结构体device 关联起来使其成为一个可以由用户空间访问和控制的设备
if (v4l2_device_register(&intf->dev, &dev->vdev) < 0)goto error;
  1. 初始化uvc设备的控制项
if (uvc_ctrl_init_device(dev) < 0)goto error;
/* 
uvc_ctrl_init_device 主要就是
1.. 遍历所有与设备管理的实体(比如处理单元PU,控制单元CU等),并且为每
个实体分配了一个 struct uvc_control *ctrl 的结构体
3. 对于每个支持的控制项,函数设置控制的索引,并调用uvc_ctrl_init_ctrl 来进一步初始化控制项。
*/
  1. 扫描设备实体(entities) 并注册成 视频链 (chains)
if (uvc_scan_device(dev) < 0)goto error;
/* 1.遍历所有实体 从输出端OT 反向扫描2.为没用分配到的实体分配视频链结构体3.初始化视频链:4.设置默认标志:
*/
  1. 为识别到的视频链注册到video_device 设备节点
uvc_register_chains(dev)	uvc_register_terms(dev, chain);uvc_register_video(dev, stream);//根据传输终端的 ID 查找视频流并将其注册为设备节点 dev/video x 

接下来分析一下uvc_register_video 这里面的函数

a. 首先对uvc_queue 进行初始化, 设置它的队列的类型,以及是否丢帧行为 (uvc_no_drop_param),根据内核结构体那张图,我们还需要将其初始化到vb2_queue 里面去

	ret = uvc_queue_init(&stream->queue, stream->type, !uvc_no_drop_param);if (ret)return ret;
/*
ret = vb2_queue_init(&queue->queue);if (ret)return ret;
*/

可以看出在UVC驱动中,主要是使用videobuf2来管理视频流的数据传输,主要包括以下几个步骤:

  • 初始化队列:UVC驱动在初始化视频流时,会创建并初始化一个vb2_queue结构体实例。这个过程包括指定队列操作的回调函数和设置缓冲区的类型(捕获或输出)。

  • 缓冲区操作:UVC驱动会使用videobuf2提供的API来执行缓冲区的排队(queue)和出队(dequeue)操作

  • 数据处理:当缓冲区准备好数据后(例如,捕获了一帧视频数据),videobuf2框架会通知UVC驱动,驱动随后可以处理这些数据,比如将其传输给用户空间。

  • 与用户空间的交互:videobuf2还处理与用户空间应用程序的交互,包括应用程序对缓冲区的请求、查询和映射操作。

b .初始化视频流 :确保视频流在开始传输数据之前已经正确配置,包括分辨率、帧率等参数

ret = uvc_video_init(stream);if (ret < 0) {uvc_printk(KERN_ERR, "Failed to initialize the device ""(%d).\n", ret);return ret;}

c. 注册视频设备

	/*设置相关的参数*/vdev->v4l2_dev = &dev->vdev;vdev->fops = &uvc_fops;vdev->ioctl_ops = &uvc_ioctl_ops;vdev->release = uvc_release;vdev->prio = &stream->chain->prio;if (stream->type == V4L2_BUF_TYPE_VIDEO_OUTPUT)vdev->vfl_dir = VFL_DIR_TX;strlcpy(vdev->name, dev->name, sizeof vdev->name);//设置视频设备的私有数据,确保在设备操作中可以访问到关联的 uvc_streaming 结构video_set_drvdata(vdev, stream);ret = video_register_device(vdev, VFL_TYPE_GRABBER, -1);if (ret < 0) {return ret;}

c. 更新视频流和链的能力

	if (stream->type == V4L2_BUF_TYPE_VIDEO_CAPTURE)stream->chain->caps |= V4L2_CAP_VIDEO_CAPTURE;elsestream->chain->caps |= V4L2_CAP_VIDEO_OUTPUT;atomic_inc(&dev->nstreams); /*更新活跃视频流的计数*/
  1. 将数据指针保存在数据接口中
usb_set_intfdata(intf, dev);
  1. 初始化中断 URB。
if ((ret = uvc_status_init(dev)) < 0) {uvc_printk(KERN_INFO, "Unable to initialize the status ""endpoint (%d), status interrupt will not be ""supported.\n", ret);}

5. UVC驱动的调用过程详解

UVC驱动通过一系列文件操作和IO控制操作(ioctl)来管理视频捕获和控制流程

5.1 核心操作集合

uvc驱动的调用过程主要是涉及之前注册时候设置的

    vdev->fops = &uvc_fops; //文件操作vdev->ioctl_ops = &uvc_ioctl_ops; //ioctl操作

文件操作(uvc_fops)

const struct v4l2_file_operations uvc_fops = {.owner		= THIS_MODULE,.open		= uvc_v4l2_open,.release	= uvc_v4l2_release,.unlocked_ioctl	= video_ioctl2,
#ifdef CONFIG_COMPAT.compat_ioctl32	= uvc_v4l2_compat_ioctl32,
#endif.read		= uvc_v4l2_read,.mmap		= uvc_v4l2_mmap,.poll		= uvc_v4l2_poll,
#ifndef CONFIG_MMU.get_unmapped_area = uvc_v4l2_get_unmapped_area,
#endif
};

IOCTL操作(uvc_ioctl_ops)

const struct v4l2_ioctl_ops uvc_ioctl_ops = {/*最基本的几个参数*/.vidioc_querycap = uvc_ioctl_querycap,.vidioc_enum_fmt_vid_cap = uvc_ioctl_enum_fmt_vid_cap,.vidioc_g_fmt_vid_cap = uvc_ioctl_g_fmt_vid_cap,.vidioc_s_fmt_vid_cap = uvc_ioctl_s_fmt_vid_cap,.vidioc_try_fmt_vid_cap = uvc_ioctl_try_fmt_vid_cap,.vidioc_reqbufs = uvc_ioctl_reqbufs,.vidioc_querybuf = uvc_ioctl_querybuf,.vidioc_qbuf = uvc_ioctl_qbuf,.vidioc_dqbuf = uvc_ioctl_dqbuf,.vidioc_streamon = uvc_ioctl_streamon,/* uvc_ioctl_streamon*1.向USB摄像头设置参数 比如使用那个format 使用这个format下的那个frame(分辨率)*1.1 根据一个uvc_streaming_control 设置数据包,可以手工设置也可以读出*1.2 调用usb_control_msg 发出数据包*2.分配设置URB*3.提交URB以接受数据*/.vidioc_streamoff = uvc_ioctl_streamoff,.vidioc_enum_fmt_vid_out = uvc_ioctl_enum_fmt_vid_out,.vidioc_g_fmt_vid_out = uvc_ioctl_g_fmt_vid_out,.vidioc_s_fmt_vid_out = uvc_ioctl_s_fmt_vid_out,.vidioc_try_fmt_vid_cap = uvc_ioctl_try_fmt_vid_cap,.vidioc_try_fmt_vid_out = uvc_ioctl_try_fmt_vid_out,.vidioc_create_bufs = uvc_ioctl_create_bufs,.vidioc_enum_input = uvc_ioctl_enum_input,.vidioc_g_input = uvc_ioctl_g_input,.vidioc_s_input = uvc_ioctl_s_input,/*查询 查询额外属性 获得 设置属性*/.vidioc_queryctrl = uvc_ioctl_queryctrl,.vidioc_query_ext_ctrl = uvc_ioctl_query_ext_ctrl,.vidioc_g_ctrl = uvc_ioctl_g_ctrl,.vidioc_s_ctrl = uvc_ioctl_s_ctrl,.vidioc_g_ext_ctrls = uvc_ioctl_g_ext_ctrls,.vidioc_s_ext_ctrls = uvc_ioctl_s_ext_ctrls,.vidioc_try_ext_ctrls = uvc_ioctl_try_ext_ctrls,.vidioc_querymenu = uvc_ioctl_querymenu,.vidioc_g_selection = uvc_ioctl_g_selection,/*设置帧间隔*/.vidioc_g_parm = uvc_ioctl _g_parm,.vidioc_s_parm = uvc_ioctl_s_parm,/*枚举支持的分辨率 帧间隔参数*/.vidioc_enum_framesizes = uvc_ioctl_enum_framesizes,.vidioc_enum_frameintervals = uvc_ioctl_enum_frameintervals,.vidioc_subscribe_event = uvc_ioctl_subscribe_event,.vidioc_unsubscribe_event = v4l2_event_unsubscribe,.vidioc_default = uvc_ioctl_default,
};

5.2 主要的调用过程

5.2.1 open

内核创建一个uvc_fh 文件句柄实例,将其关联到特定的视频流,以及初始化V4L2文件句柄

static int uvc_v4l2_open(struct file *file)

5.2.2 VIDIOC_QUERYCAP 查询设备的功能

static int uvc_ioctl_querycap(struct file *file, void *fh,struct v4l2_capability *cap)
{
...usb_make_path(stream->dev->udev, cap->bus_info, sizeof(cap->bus_info));cap->capabilities = V4L2_CAP_DEVICE_CAPS | V4L2_CAP_STREAMING| chain->caps;if (stream->type == V4L2_BUF_TYPE_VIDEO_CAPTURE)cap->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;elsecap->device_caps = V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_STREAMING;return 0;
}

5.2.3 VIDIOC_ENUM_FMT 枚举设备支持的格式

枚举设备支持的格式 ,比如MJPEG或H264等 填充到v4l2_fmtdesc *fmt

static int uvc_ioctl_enum_fmt(struct uvc_streaming *stream,struct v4l2_fmtdesc *fmt)
{struct uvc_format *format;enum v4l2_buf_type type = fmt->type;__u32 index = fmt->index;if (fmt->type != stream->type || fmt->index >= stream->nformats)return -EINVAL;memset(fmt, 0, sizeof(*fmt)); //将fmt结构内存清0 fmt->index = index;fmt->type = type;format = &stream->format[fmt->index];fmt->flags = 0;if (format->flags & UVC_FMT_FLAG_COMPRESSED)fmt->flags |= V4L2_FMT_FLAG_COMPRESSED;strlcpy(fmt->description, format->name, sizeof(fmt->description));fmt->description[sizeof(fmt->description) - 1] = 0;fmt->pixelformat = format->fcc;return 0;
}

5.2.4 VIDIOC_G_FMT 得到当前的支持的格式 比如分辨率等

int uvc_ioctl_g_fmt_vid_cap(struct file *file, void *fh, struct v4l2_format *fmt)
--------->	uvc_v4l2_get_format(stream, fmt); /*跳转到v4l2框架下的函数来进行*/
static int uvc_v4l2_get_format(struct uvc_streaming *stream,struct v4l2_format *fmt)
{
...fmt->fmt.pix.pixelformat = format->fcc;fmt->fmt.pix.width = frame->wWidth;fmt->fmt.pix.height = frame->wHeight;fmt->fmt.pix.field = V4L2_FIELD_NONE; //逐行扫描fmt->fmt.pix.bytesperline = format->bpp * frame->wWidth / 8;fmt->fmt.pix.sizeimage = stream->ctrl.dwMaxVideoFrameSize;fmt->fmt.pix.colorspace = format->colorspace;//色彩空间fmt->fmt.pix.priv = 0;return ret;
...
}

5.2.5 VIDIOC_S_FMT 更新UVC视频流的当前格式和帧大小 以匹配应用程序的请求配置

static int uvc_ioctl_s_fmt_vid_cap(struct file *file, void *fh, struct v4l2_format *fmt)
-------> uvc_v4l2_set_format(stream, fmt);
/*
struct uvc_streaming *stream: 视频流对象的指针。
struct v4l2_format *fmt: 应用程序请求的视频格式信息。
*/
static int uvc_v4l2_set_format(struct uvc_streaming *stream,struct v4l2_format *fmt)
{/*尝试匹配和调整应用请求的格式*/ret = uvc_v4l2_try_format(stream, fmt, &probe, &format, &frame);mutex_lock(&stream->mutex);if (uvc_queue_allocated(&stream->queue)) {/*如果视频流帧缓冲区已经分配,此时不能修改格式*/ret = -EBUSY;}
}

5.2.6 VIDIOC_TRY_FMT 检查格式是否匹配

static int uvc_ioctl_try_fmt_vid_cap(struct file *file, void *fh, struct v4l2_format *fmt)
-------> uvc_v4l2_try_format(stream, fmt, &probe, NULL, NULL);

5.2.3 VIDIOC_REQBUF

static int uvc_ioctl_reqbufs(struct file *file, void *fh, struct v4l2_requestbuffers *rb)
-------> uvc_request_buffers(&stream->queue, rb);
--------->vb2_reqbufs(&queue->queue, rb); //跳转到video_buf2
-----------> __reqbufs(q, req)
--------------> __vb2_queue_alloc(q, req->memory, num_buffers, num_planes)
/*
struct vb2_queue *q: 指向视频缓冲区vb2队列对象的指针。
struct v4l2_requestbuffers *req: 包含了请求分配缓冲区的信息。
*/int vb2_reqbufs(struct vb2_queue *q, struct v4l2_requestbuffers *req)
{/*验证请求的内存类型和视频流的类型是否匹配*/int ret = __verify_memory_type(q, req->memory, req->type);return ret ? ret : __reqbufs(q, req);
}
__reqbufs() 启动流式传输

应从驱动程序的 vidioc_reqbufs ioctl 处理程序中调用。
该函数

  1. 验证从用户空间传递的流参数、
  2. 设置队列、
  3. 与驱动程序协商缓冲区数量和每个缓冲区的平面数
  4. 根据商定的参数分配内部缓冲区结构(struct vb2_buffer)。
  5. 对于 MMAP 内存类型,使用队列初始化过程中提供的内存处理/分配例程分配实际视频内存

如果 req->count 为 0,则释放所有内存。
如果队列之前已被分配(通过之前的 vb2_reqbufs)调用
且队列不忙,内存将被重新分配。

该函数的返回值可直接从 从驱动程序中的 vidioc_reqbufs 处理程序直接返回。

static int __reqbufs(struct vb2_queue *q, struct v4l2_requestbuffers *req)
{unsigned int num_buffers, allocated_buffers, num_planes = 0;int ret;if (q->streaming) {dprintk(1, "streaming active\n");return -EBUSY;}if (req->count == 0 || q->num_buffers != 0 || q->memory != req->memory) {/** We already have buffers allocated, so first check if they* are not in use and can be freed.*/mutex_lock(&q->mmap_lock);if (q->memory == V4L2_MEMORY_MMAP && __buffers_in_use(q)) {mutex_unlock(&q->mmap_lock);dprintk(1, "memory in use, cannot free\n");return -EBUSY;}/** Call queue_cancel to clean up any buffers in the PREPARED or* QUEUED state which is possible if buffers were prepared or* queued without ever calling STREAMON.*/__vb2_queue_cancel(q);ret = __vb2_queue_free(q, q->num_buffers);mutex_unlock(&q->mmap_lock);if (ret)return ret;/** In case of REQBUFS(0) return immediately without calling* driver's queue_setup() callback and allocating resources.*/if (req->count == 0)return 0;}/** Make sure the requested values and current defaults are sane.*/num_buffers = min_t(unsigned int, req->count, VIDEO_MAX_FRAME);num_buffers = max_t(unsigned int, num_buffers, q->min_buffers_needed);memset(q->plane_sizes, 0, sizeof(q->plane_sizes));memset(q->alloc_ctx, 0, sizeof(q->alloc_ctx));q->memory = req->memory;/** Ask the driver how many buffers and planes per buffer it requires.* Driver also sets the size and allocator context for each plane.*/ret = call_qop(q, queue_setup, q, NULL, &num_buffers, &num_planes,q->plane_sizes, q->alloc_ctx);if (ret)return ret;/* Finally, allocate buffers and video memory */allocated_buffers = __vb2_queue_alloc(q, req->memory, num_buffers, num_planes);if (allocated_buffers == 0) {dprintk(1, "memory allocation failed\n");return -ENOMEM;}/** There is no point in continuing if we can't allocate the minimum* number of buffers needed by this vb2_queue.*/if (allocated_buffers < q->min_buffers_needed)ret = -ENOMEM;/** Check if driver can handle the allocated number of buffers.*/if (!ret && allocated_buffers < num_buffers) {num_buffers = allocated_buffers;ret = call_qop(q, queue_setup, q, NULL, &num_buffers,&num_planes, q->plane_sizes, q->alloc_ctx);if (!ret && allocated_buffers < num_buffers)ret = -ENOMEM;/** Either the driver has accepted a smaller number of buffers,* or .queue_setup() returned an error*/}mutex_lock(&q->mmap_lock);q->num_buffers = allocated_buffers;if (ret < 0) {/** Note: __vb2_queue_free() will subtract 'allocated_buffers'* from q->num_buffers.*/__vb2_queue_free(q, allocated_buffers);mutex_unlock(&q->mmap_lock);return ret;}mutex_unlock(&q->mmap_lock);/** Return the number of successfully allocated buffers* to the userspace.*/req->count = allocated_buffers;q->waiting_for_buffers = !V4L2_TYPE_IS_OUTPUT(q->type);return 0;
}
__vb2_queue_alloc -----分配videobuf 结构体

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

读取Excel的封装方法

步骤&#xff1a; 1、我们需要将得到的结果存储到list集合中&#xff0c;所以实例化一个ArrayList类 List<CaseInfo> list new ArrayList<CaseInfo>();//实例化一个类 常规下在list里添加内容即可 不再是字符串类&#xff0c;是caseinfo用例 list.add里添加casei…

C语言 - 各种自定义数据类型

1.结构体 把不同类型的数据组合成一个整体 所占内存长度是各成员所占内存的总和 typedef struct XXX { int a; char b; }txxx; txxx data; typedef struct XXX { int a:1; int b:1; …

ARM64汇编05 - MOV系列指令

MOV(wide immediate) MOV 可以将一个立即数移动到寄存器中。 .text:0000000000000834 80 46 82 D2 MOV X0, #0x1234 ; Keypatch modified this from:MOV X0, #0x1234 对应的汇编代码为&#xff1a;80 46 82 D2 看手册可知&#xf…

发布DDD脚手架到Maven仓库,IntelliJ IDEA 配置一下即可使用

作者&#xff1a;小傅哥 博客&#xff1a;https://bugstack.cn 项目&#xff1a;https://gaga.plus 沉淀、分享、成长&#xff0c;让自己和他人都能有所收获&#xff01;&#x1f604; 大家好&#xff0c;我是技术UP主&#xff0c;小傅哥。 这篇文章将帮助粉丝伙伴们更高效地利…

科技回顾,飞凌嵌入式受邀亮相第八届瑞芯微开发者大会「RKDC2024」

2024年3月7日~8日&#xff0c;第八届瑞芯微开发者大会&#xff08;RKDC2024&#xff09;在福州举行&#xff0c;本届大会以“AI芯片AI应用AloT”为主题&#xff0c;邀请各行业的开发者共启数智化未来。 本届大会亮点颇多&#xff0c;不仅有13大芯片应用展示、9场产品和技术论坛…

Pytorch Tutorial

本教程将详细展示如何使用PyTorch训练神经网络&#xff0c;并给出完整代码和关键注释&#xff08;ipython&#xff09;&#xff0c;建议使用CoLab的GPU来编译代码。 目录 环境初始化数据准备模型搭建模型训练优化器训练和评估函数开始训练 可视化 环境初始化 !pip install tor…

Linux学习:基础开发工具的使用(1)

目录 1. Linux软件包管理器&#xff1a;yum工具1.1 yum是什么&#xff08;软件商城&#xff09;1.2 yum的使用1.3 yum的背景生态 2. 项目开发与集成开发环境3. vim编辑器3.1 vim编辑器的常见模式与模式切换3.3 vim编辑器的使用3.3.1 命令模式下的常见命令&#xff1a;3.3.2 vim…

基于YOLOv8/YOLOv7/YOLOv6/YOLOv5的石头剪刀布手势识别系统详解(深度学习模型+UI界面代码+训练数据集)

摘要&#xff1a;本篇博客深入探讨了使用深度学习技术开发石头剪刀布手势识别系统的过程&#xff0c;并分享了完整代码。该系统利用先进的YOLOv8、YOLOv7、YOLOv6、YOLOv5算法&#xff0c;并对这几个版本进行性能对比&#xff0c;如mAP、F1 Score等关键指标。文章详细阐述了YOL…

vscode使用npm命令无反应,而终端可以的解决办法

如若你遇到这种情况 使用命令 get-command npm 去下面这个路径把它删掉就可以了

HarmonyOS的功能及场景应用

一、基本介绍 鸿蒙HarmonyOS主要应用的设备包括智慧屏、平板、手表、智能音箱、IoT设备等。具体来说&#xff0c;鸿蒙系统是一款面向全场景(移动办公、运动健康、社交通信、媒体娱乐等)的分布式操作系统&#xff0c;能够支持手机、平板、智能穿戴、智慧屏、车机等多种终端设备…

《量子计算:下一个大风口,还是一个热炒概念?》

引言 量子计算,作为一项颠覆性的技术,一直以来备受关注。它被认为是未来计算领域的一次革命,可能改变我们对计算能力和数据处理的理解。然而,随着技术的不断进步和商业应用的探索,人们开始思考,量子计算到底是一个即将到来的大风口,还是一个被过度炒作的概念? 量子计…

工业物联网平台在水务环保、暖通制冷、电力能源等行业的应用

随着科技的不断发展&#xff0c;工业物联网平台作为连接物理世界与数字世界的桥梁&#xff0c;正逐渐成为推动各行业智能化转型的关键力量。在水务环保、暖通制冷、电力能源等行业&#xff0c;工业物联网平台的应用尤为广泛&#xff0c;对于提升运营效率、降低能耗、优化管理等…

16. C++标准库

C标准库兼容C语言标准函数库&#xff0c;可以在C标准库中直接使用C语言标准函数库文件&#xff0c;同时C标准库增加了自己的源代码文件&#xff0c;新增文件使用C编写&#xff0c;多数代码放在std命名空间中&#xff0c;所以连接C标准库文件后还需要 using namespace std;。 【…

【RabbitMQ】RabbitMQ的交换机

交换机类型 在上文中&#xff0c;都没有交换机&#xff0c;生产者直接发送消息到队列。而一旦引入交换机&#xff0c;消息发送的模式会有很大变化&#xff1a;可以看到&#xff0c;在订阅模型中&#xff0c;多了一个exchange角色&#xff0c;而且过程略有变化&#xff1a; Pub…

【wps】wps与office办公函数储备使用(结合了使用案例 持续更新)

【wps】wps与office办公函数储备使用(结合了使用案例 持续更新) 1、TODAY函数 返回当前电脑系统显示的日期 TODAY函数&#xff1a;表示返回当前电脑系统显示的日期。 公式用法&#xff1a;TODAY() 2、NOW函数 返回当前电脑系统显示的日期和时间 NOW函数&#xff1a;表示返…

Day29:安全开发-JS应用DOM树加密编码库断点调试逆向分析元素属性操作

目录 JS原生开发-DOM树-用户交互 JS导入库开发-编码加密-逆向调试 思维导图 JS知识点&#xff1a; 功能&#xff1a;登录验证&#xff0c;文件操作&#xff0c;SQL操作&#xff0c;云应用接入&#xff0c;框架开发&#xff0c;打包器使用等 技术&#xff1a;原生开发&#x…

GaussDB(DWS)运维利刃:TopSQL工具解析

在生产环境中&#xff0c;难免会面临查询语句出现异常中断、阻塞时间长等突发问题&#xff0c;如果没能及时记录信息&#xff0c;事后就需要投入更多的人力及时间成本进行问题的定位和解决&#xff0c;有时还无法定位到错误出现的地方。在本期《GaussDB(DWS)运维利刃&#xff1…

在 Python 中从键盘读取用户输入

文章目录 如何在 Python 中从键盘读取用户输入input 函数使用input读取键盘输入使用input读取特定类型的数据处理错误从用户输入中读取多个值 getpass 模块使用 PyInputPlus 自动执行用户输入评估总结 如何在 Python 中从键盘读取用户输入 原文《How to Read User Input From t…

小家电显示驱动芯片SM1616特点与相关型号推荐

电饭煲、电磁炉、空调和机顶盒等等小家电通常需要使用显示驱动芯片来控制和驱动显示屏。这些显示驱动芯片的主要功能是将处理器的信号转换成显示屏能够理解的信号&#xff0c;从而显示出相应的文字和图像。 具体来说&#xff0c;电饭煲、电磁炉、空调等家等小家电通常会有一个或…

四桥臂三相逆变器动态电压恢复器(DVR)MATLAB仿真

微❤关注“电气仔推送”获得资料&#xff08;专享优惠&#xff09; 简介 四桥臂三相逆变器 电路 的一般形式如图 1&#xff0c;为 便于分析 &#xff0c;将其等效成图所示的电路 。以直流母线电压Ud的 1&#xff0f;2处为参考点 &#xff0c;逆变器三相和零线相 输 出可等效成…