ARM Linux DIY(十四)摄像头捕获画面显示到屏幕

news2024/11/25 11:28:20

前言

前期已经调试好了摄像头和屏幕,今天我们将摄像头捕获的画面显示到屏幕上。

原理

摄像头对应 /dev/video0,屏幕对应 /dev/fb0,所以我们只要写一个应用程序,读取 video0 写入到 fb0 就可以了。

应用程序代码实例

camera_display.c

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <ctype.h>
#include <errno.h>
#include <sys/mman.h>
#include <sys/time.h>
#include <asm/types.h>
#include <linux/videodev2.h>
#include <linux/fb.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <poll.h>
#include <math.h>
#include <wchar.h>
#include <time.h>
#include <stdbool.h>

#define CAM_WIDTH 800
#define CAM_HEIGHT 600
#define YUVToRGB(Y) ((u16)((((u8)(Y) >> 3) << 11) | (((u8)(Y) >> 2) << 5) | ((u8)(Y) >> 3)))

static char *dev_video;
static char *dev_fb0;

static char *yuv_buffer;
static char *rgb_buffer;
typedef unsigned int u32;
typedef unsigned short u16;
typedef unsigned char u8;
struct v4l2_buffer video_buffer;
int lcd_fd;
int video_fd;
unsigned char *lcd_mem_p = NULL; //保存LCD屏映射到进程空间的首地址
struct fb_var_screeninfo vinfo;
struct fb_fix_screeninfo finfo;
char *video_buff_buff[4]; /*保存摄像头缓冲区的地址*/
int video_height = 0;
int video_width = 0;
unsigned char *lcd_display_buff; //LCD显存空间
unsigned char *lcd_display_buff2; //LCD显存空间

static void errno_exit(const char *s)
{
	fprintf(stderr, "%s error %d, %s\n", s, errno, strerror(errno));
	exit(EXIT_FAILURE);
}

static int video_init(void)
{
	struct v4l2_capability cap;
	struct v4l2_fmtdesc dis_fmtdesc;
	struct v4l2_format video_format;
	struct v4l2_requestbuffers video_requestbuffers;
	struct v4l2_buffer video_buffer;

	ioctl(video_fd, VIDIOC_QUERYCAP, &cap);
	
	dis_fmtdesc.index = 0;
	dis_fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	// printf("-----------------------支持格式---------------------\n");
	// while (ioctl(video_fd, VIDIOC_ENUM_FMT, &dis_fmtdesc) != -1) {
	// 	printf("\t%d.%s\n", dis_fmtdesc.index + 1, dis_fmtdesc.description);
	// 	dis_fmtdesc.index++;
	// }

	video_format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	video_format.fmt.pix.width = CAM_WIDTH;
	video_format.fmt.pix.height = CAM_HEIGHT;
	video_format.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; //使用JPEG格式帧,用于静态图像采集

	ioctl(video_fd, VIDIOC_S_FMT, &video_format);

	printf("当前摄像头支持的分辨率:%dx%d\n", video_format.fmt.pix.width, video_format.fmt.pix.height);
	if (video_format.fmt.pix.pixelformat != V4L2_PIX_FMT_YUYV) {
		printf("当前摄像头不支持YUYV格式输出.\n");
		video_height = video_format.fmt.pix.height;
		video_width = video_format.fmt.pix.width;
		//return -3;
	} else {
		video_height = video_format.fmt.pix.height;
		video_width = video_format.fmt.pix.width;
		printf("当前摄像头支持YUYV格式输出.width %d height %d\n", video_height, video_height);
	}

	/*3. 申请缓冲区*/
	memset(&video_requestbuffers, 0, sizeof(struct v4l2_requestbuffers));
	video_requestbuffers.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	video_requestbuffers.count = 4;
	video_requestbuffers.memory = V4L2_MEMORY_MMAP;
	if (ioctl(video_fd, VIDIOC_REQBUFS, &video_requestbuffers))
		return -4;
	printf("成功申请的缓冲区数量:%d\n", video_requestbuffers.count);

	/*4. 得到每个缓冲区的地址: 将申请的缓冲区映射到进程空间*/
	memset(&video_buffer, 0, sizeof(struct v4l2_buffer));
	int i;
	for (i = 0; i < video_requestbuffers.count; i++) {
		video_buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
		video_buffer.index = i;
		video_buffer.memory = V4L2_MEMORY_MMAP;
		if (ioctl(video_fd, VIDIOC_QUERYBUF, &video_buffer))
			return -5;
		/*映射缓冲区的地址到进程空间*/
		video_buff_buff[i] =
			mmap(NULL, video_buffer.length, PROT_READ | PROT_WRITE, MAP_SHARED, video_fd, video_buffer.m.offset);
		printf("第%d个缓冲区地址:%#X\n", i, video_buff_buff[i]);
	}

	/*5. 将缓冲区放入到采集队列*/
	memset(&video_buffer, 0, sizeof(struct v4l2_buffer));
	for (i = 0; i < video_requestbuffers.count; i++) {
		video_buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
		video_buffer.index = i;
		video_buffer.memory = V4L2_MEMORY_MMAP;
		if (ioctl(video_fd, VIDIOC_QBUF, &video_buffer)) {
			printf("VIDIOC_QBUF error\n");
			return -6;
		}
	}

	/*6. 启动摄像头采集*/
	printf("启动摄像头采集\n");
	int opt_type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	if (ioctl(video_fd, VIDIOC_STREAMON, &opt_type)) {
		printf("VIDIOC_STREAMON error\n");
		return -7;
	}

	return 0;
}

int lcd_init(void)
{
	/*2. 获取可变参数*/
	if (ioctl(lcd_fd, FBIOGET_VSCREENINFO, &vinfo))
		return -2;
	printf("屏幕X:%d   屏幕Y:%d  像素位数:%d\n", vinfo.xres, vinfo.yres, vinfo.bits_per_pixel);
	//分配显存空间,完成图像显示
	lcd_display_buff = malloc(vinfo.xres * vinfo.yres * vinfo.bits_per_pixel / 8);

	/*3. 获取固定参数*/
	if (ioctl(lcd_fd, FBIOGET_FSCREENINFO, &finfo))
		return -3;
	finfo.smem_len = 115200;
	finfo.line_length = 480;
	printf("smem_len=%d Byte,line_length=%d Byte\n", finfo.smem_len, finfo.line_length);

	/*4. 映射LCD屏物理地址到进程空间*/
	lcd_mem_p =
		(unsigned char *)mmap(0, finfo.smem_len, PROT_READ | PROT_WRITE, MAP_SHARED, lcd_fd, 0); //从文件的那个地方开始映射
	memset(lcd_mem_p, 0xFFFFFFFF, finfo.smem_len);
	printf("映射LCD屏物理地址到进程空间\n");

	return 0;
}

static void close_device(void)
{
	if (-1 == close(video_fd))
		errno_exit("close");
	video_fd = -1;

	if (-1 == close(lcd_fd))
		errno_exit("close");
	lcd_fd = -1;
}

static void open_device(void)
{
	video_fd = open(dev_video, O_RDWR /* required */ | O_NONBLOCK, 0);
	if (-1 == video_fd) {
		fprintf(stderr, "Cannot open '%s': %d, %s\n", dev_video, errno, strerror(errno));
		exit(EXIT_FAILURE);
	}

	lcd_fd = open(dev_fb0, O_RDWR, 0);
	if (-1 == lcd_fd) {
		fprintf(stderr, "Cannot open '%s': %d, %s\n", dev_fb0, errno, strerror(errno));
		exit(EXIT_FAILURE);
	}
}

/* 将YUV格式数据转为RGB */
void yuv_to_rgb(unsigned char *yuv_buffer, unsigned char *rgb_buffer, int iWidth, int iHeight)
{
	int x;
	int z = 0;
	unsigned char *ptr = rgb_buffer;
	unsigned char *yuyv = yuv_buffer;
	int r, g, b;
	int y, u, v;

	for (x = 0; x < iWidth * iHeight; x++) {
		if (!z)
			y = yuyv[0] << 8;
		else
			y = yuyv[2] << 8;
		u = yuyv[1] - 128;
		v = yuyv[3] - 128;
		r = (y + (359 * v)) >> 8;
		g = (y - (88 * u) - (183 * v)) >> 8;
		b = (y + (454 * u)) >> 8;
		*(ptr++) = (b > 255) ? 255 : ((b < 0) ? 0 : b);
		*(ptr++) = (g > 255) ? 255 : ((g < 0) ? 0 : g);
		*(ptr++) = (r > 255) ? 255 : ((r < 0) ? 0 : r);
		if (z++) {
			z = 0;
			yuyv += 4;
		}
	}
}

void rgb24_to_rgb565(char *rgb24, char *rgb16)
{
	int i = 0, j = 0;

	for (i = 0; i < 240 * 240 * 3; i += 3) {
		rgb16[j] = rgb24[i] >> 3; // B
		rgb16[j] |= ((rgb24[i + 1] & 0x1C) << 3); // G
		rgb16[j + 1] = rgb24[i + 2] & 0xF8; // R
		rgb16[j + 1] |= (rgb24[i + 1] >> 5); // G

		j += 2;
	}
}

int main(int argc, char **argv)
{
	struct pollfd video_fds;

	dev_video = "/dev/video0";
	dev_fb0 = "/dev/fb0";

	open_device();
	video_init();
	lcd_init();

	/* 读取摄像头的数据*/
	video_fds.events = POLLIN;
	video_fds.fd = video_fd;

	memset(&video_buffer, 0, sizeof(struct v4l2_buffer));
	rgb_buffer = malloc(CAM_WIDTH * CAM_HEIGHT * 3);
	yuv_buffer = malloc(CAM_WIDTH * CAM_HEIGHT * 3);

	while (1) {
		/*等待摄像头采集数据*/
		poll(&video_fds, 1, -1);
		/*得到缓冲区的编号*/
		video_buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
		video_buffer.memory = V4L2_MEMORY_MMAP;
		ioctl(video_fd, VIDIOC_DQBUF, &video_buffer);
		printf("当前采集OK的缓冲区编号:%d,地址:%#X num:%d\n", video_buffer.index, video_buff_buff[video_buffer.index],
		       strlen(video_buff_buff[video_buffer.index]));
		/*对缓冲区数据进行处理*/
		yuv_to_rgb(video_buff_buff[video_buffer.index], yuv_buffer, video_height, video_width);
		rgb24_to_rgb565(yuv_buffer, rgb_buffer);
		printf("显示屏进行显示\n");
		//显示屏进行显示: 将显存空间的数据拷贝到LCD屏进行显示
		memcpy(lcd_mem_p, rgb_buffer, vinfo.xres * vinfo.yres * vinfo.bits_per_pixel / 8);
		/*将缓冲区放入采集队列*/
		ioctl(video_fd, VIDIOC_QBUF, &video_buffer);
		printf("将缓冲区放入采集队列\n");
	}

	/* 关闭设备*/
	close_device();

	return 0;
}

调试

# ./camera_display2.out 
当前摄像头支持的分辨率:800x600
当前摄像头支持YUYV格式输出.width 600 height 600
[   52.859327] sun6i-csi 1cb4000.csi: Unsupported pixformat: 0x56595559 with mbus code: 0x2006!
成功申请的缓冲区数量:4
第0个缓冲区地址:0XB6D6A000
第1个缓冲区地址:0XB6C7F000
第2个缓冲区地址:0XB6B94000
第3个缓冲区地址:0XB6AA9000
启动摄像头采集
VIDIOC_STREAMON error
屏幕X:240   屏幕Y:240  像素位数:16
smem_len=115200 Byte,line_length=480 Byte
映射LCD屏物理地址到进程空间
当前采集OK的缓冲区编号:0,地址:0XB6D6A000 num:0
显示屏进行显示
将缓冲区放入采集队列
当前采集OK的缓冲区编号:0,地址:0XB6D6A000 num:0

运行报错 sun6i-csi 1cb4000.csi: Unsupported pixformat: 0x56595559 with mbus code: 0x2006!
并且屏幕显示一片绿
在这里插入图片描述

猜测 1:会不会是设备树配置不对
仔细检查设备树参数,还真发现了一处错误

&i2c1 {
	pinctrl-0 = <&i2c1_pins>;
	pinctrl-names = "default";
	clock-frequency = <400000>;
	status = "okay";
	
	ov2640: camera@30 {
		compatible = "ovti,ov2640";
		reg = <0x30>;
		pinctrl-names = "default";
		pinctrl-0 = <&csi1_mclk_pin>;
		clocks = <&ccu CLK_CSI1_MCLK>;
		clock-names = "xvclk";
		assigned-clocks = <&ccu CLK_CSI1_MCLK>;
		assigned-clock-rates = <26000000>;
		port {
			ov2640_0: endpoint {
				remote-endpoint = <&csi1_ep>;
				bus-width = <10>;
			};
		};
	};
};

时钟频率 26000000 是我之前使用 26MHz 晶振时改的,后来由于 USB 问题,晶振换成了 24MHz 的,这里没有同步修改,那就改成 24000000
运行,还是报同样的错误。
猜测 2:是不是应用程序中摄像头分辨率设置的是 800x600,而屏幕是 240x240 导致的
应用程序摄像头分辨率改成 240x240

#define CAM_WIDTH 240
#define CAM_HEIGHT 240

运行,结果还是报同样的错误

方案 3:上网搜索
并没有找到类似问题。

方案 4:看代码
没办法只能看代码了
根据内核报错信息 Unsupported pixformat 找到
drivers/media/platform/sunxi/sun6i-csi/sun6i_video.c

static int sun6i_video_link_validate(struct media_link *link)
{
	struct video_device *vdev = container_of(link->sink->entity,
						 struct video_device, entity);
	struct sun6i_video *video = video_get_drvdata(vdev);
	struct v4l2_subdev_format source_fmt;
	int ret;

	video->mbus_code = 0;

	if (!media_entity_remote_pad(link->sink->entity->pads)) {
		dev_info(video->csi->dev,
			 "video node %s pad not connected\n", vdev->name);
		return -ENOLINK;
	}

	ret = sun6i_video_link_validate_get_format(link->source, &source_fmt);
	if (ret < 0)
		return ret;

	if (!sun6i_csi_is_format_supported(video->csi,
					   video->fmt.fmt.pix.pixelformat,
					   source_fmt.format.code)) {
		dev_err(video->csi->dev,
			"Unsupported pixformat: 0x%x with mbus code: 0x%x!\n",
			video->fmt.fmt.pix.pixelformat,
			source_fmt.format.code);
		return -EPIPE;
	}

	if (source_fmt.format.width != video->fmt.fmt.pix.width ||
	    source_fmt.format.height != video->fmt.fmt.pix.height) {
		dev_err(video->csi->dev,
			"Wrong width or height %ux%u (%ux%u expected)\n",
			video->fmt.fmt.pix.width, video->fmt.fmt.pix.height,
			source_fmt.format.width, source_fmt.format.height);
		return -EPIPE;
	}

	video->mbus_code = source_fmt.format.code;

	return 0;
}

在判断 sun6i_csi_is_format_supported() 处出问题了,追了下代码,发现最后是个宏函数就不想追了,索性将这段注释掉,
运行,

# ./camera_display2.out 
当前摄像头支持的分辨率:240x240[   64.538654] sun6i-csi 1cb4000.csi: Wrong width or height 240x240 (800x600 expected)

结果又报 Wrong width or height 240x240 (800x600 expected) 错误,和上面一样,注释掉先让代码跑通,
运行,不报错了,屏幕也开始显示图像了
在这里插入图片描述
但是这图像明显不对啊,不过至少有进步了
那就继续追代码,先将那两处判断恢复。
跟着代码,一路追到获取摄像头参数的地方
drivers/media/i2c/ov2640.c

static int ov2640_get_fmt(struct v4l2_subdev *sd,
		struct v4l2_subdev_pad_config *cfg,
		struct v4l2_subdev_format *format)
{
	struct v4l2_mbus_framefmt *mf = &format->format;
	struct i2c_client  *client = v4l2_get_subdevdata(sd);
	struct ov2640_priv *priv = to_ov2640(client);

	if (format->pad)
		return -EINVAL;

	if (format->which == V4L2_SUBDEV_FORMAT_TRY) {
#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API
		mf = v4l2_subdev_get_try_format(sd, cfg, 0);
		format->format = *mf;
		return 0;
#else
		return -ENOTTY;
#endif
	}

	mf->width	= priv->win->width;
	mf->height	= priv->win->height;
	mf->code	= priv->cfmt_code; // 这行
	mf->colorspace	= V4L2_COLORSPACE_SRGB;
	mf->field	= V4L2_FIELD_NONE;
	mf->ycbcr_enc	= V4L2_YCBCR_ENC_DEFAULT;
	mf->quantization = V4L2_QUANTIZATION_DEFAULT;
	mf->xfer_func	= V4L2_XFER_FUNC_DEFAULT;

	return 0;
}

重点是 mf->code = priv->cfmt_code; 这行,报错信息 with mbus code: 0x2006 中的 0x2006 应该就是该值,
继续追

/*
 * i2c_driver functions
 */
static int ov2640_probe(struct i2c_client *client,
			const struct i2c_device_id *did)
{
	struct ov2640_priv	*priv;
	struct i2c_adapter	*adapter = client->adapter;
	int			ret;

	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) {
		dev_err(&adapter->dev,
			"OV2640: I2C-Adapter doesn't support SMBUS\n");
		return -EIO;
	}

	priv = devm_kzalloc(&client->dev, sizeof(*priv), GFP_KERNEL);
	if (!priv)
		return -ENOMEM;

	if (client->dev.of_node) {
		priv->clk = devm_clk_get(&client->dev, "xvclk");
		if (IS_ERR(priv->clk))
			return PTR_ERR(priv->clk);
		ret = clk_prepare_enable(priv->clk);
		if (ret)
			return ret;
	}

	ret = ov2640_probe_dt(client, priv);
	if (ret)
		goto err_clk;

	priv->win = ov2640_select_win(SVGA_WIDTH, SVGA_HEIGHT);
	priv->cfmt_code = MEDIA_BUS_FMT_UYVY8_2X8; // 这行

cfmt_code 是在这里被赋值的,其中 MEDIA_BUS_FMT_UYVY8_2X8 值是 0x2006

#define MEDIA_BUS_FMT_UYVY8_2X8			0x2006

而我们要使用的是 YUYV 格式,那就将这里改掉,改成 YUYV

// priv->cfmt_code = MEDIA_BUS_FMT_UYVY8_2X8;
priv->cfmt_code = MEDIA_BUS_FMT_YUYV8_2X8;

运行,

/root # ./camera_display2.out 
当前摄像头支持的分辨率:240x240[   55.298859] sun6i-csi 1cb4000.csi: Wrong width or height 240x240 (800x600 expected)

格式问题看起来解了,那就继续解分辨率的问题
drivers/media/i2c/ov2640.c

// mf->width	= win->width;
	// mf->height	= win->height;
	mf->width	= 240;
	mf->height	= 240;

将 ov2640.c 文件中关于分辨率的设置,都硬编码为 240x240
运行
在这里插入图片描述
成功了,不过发现图像方向和屏幕方向不一致,
没找到摄像头旋转的方法,最终旋转屏幕实现了方向一致
在这里插入图片描述
至此,摄像头捕获的画面可以实时显示到屏幕了。

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

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

相关文章

[PyTorch][chapter 55][WGAN]

前言&#xff1a; 前面讲到GAN 在训练生成器的时候&#xff0c;如果当前的Pr 和 Pg 的分布不重叠场景下&#xff1a; JS散度为一个固定值&#xff0c;梯度为0&#xff0c;导致无法更新生成器G WGAN的全称是WassersteinGAN&#xff0c;它提出了用Wasserstein距离&#xff08;也…

第2章 算法

2.1 开场白 2.2 数据结构与算法之间的关系 在“数据结构”课程中&#xff0c;就算谈到算法&#xff0c;也是为了帮助理解好数据结构&#xff0c;并不会详细谈及算法的方方面面。 2.3 两种算法的比较 2.4 算法的定义 算法是解决特定问题求解步骤的描述&#xff0c;在计算机…

【AI视野·今日Robot 机器人论文速览 第四十一期】Tue, 26 Sep 2023

AI视野今日CS.Robotics 机器人学论文速览 Tue, 26 Sep 2023 Totally 73 papers &#x1f449;上期速览✈更多精彩请移步主页 Daily Robotics Papers Extreme Parkour with Legged Robots Authors Xuxin Cheng, Kexin Shi, Ananye Agarwal, Deepak Pathak人类可以通过以高度动态…

华为智能企业远程办公安全解决方案(1)

华为智能企业远程办公安全解决方案&#xff08;1&#xff09; 课程地址方案背景需求分析企业远程办公业务概述企业远程办公安全风险分析企业远程办公环境搭建需求分析 方案设计组网架构设备选型方案亮点 课程地址 本方案相关课程资源已在华为O3社区发布&#xff0c;可按照以下…

shell脚本学习笔记

shell脚本重点记录 判断文件或者文件夹是否存在 if [ ! -d "log" ];thenchmod 707 $file1一个文件的权限包括读取、写入、执行&#xff0c;权限范围包含所有者、所属组、其他人&#xff0c;可以通过数字或者字母描述一个文件的权限&#xff1a;读取权限对应r或4&a…

高速,低延,任意频丨庚顿新一代实时数据库鼎力支撑电力装备服务数字化

产品同质化日趋严重以及市场需求不断迭代等内外形势下&#xff0c;电力装备制造业自身赢利需求不断增涨&#xff0c;电力等下游产业数字化发展形成倒逼之态&#xff0c;作为国家未来发展的高端装备创新工程主战场&#xff0c;电力装备智能化以及服务型转型升级已经成为装备制造…

在nodejs中如何防止ssrf攻击

在nodejs中如何防止ssrf攻击 什么是ssrf攻击 ssrf&#xff08;server-side request forgery&#xff09;是服务器端请求伪造&#xff0c;指攻击者能够从易受攻击的Web应用程序发送精心设计的请求的对其他网站进行攻击。(利用一个可发起网络请求的服务当作跳板来攻击其他服务)…

mac docker部署hadoop集群

1. 安装docker 确保电脑已经安装docker docker安装过程可自行查找资料&#xff0c;mac下docker可以使用brew命令安装 安装之后&#xff0c;查看docker版本&#xff0c;确认安装成功 docker -v2. 下载jdk 最好下载jdk-8&#xff0c;jdk的版本过高可能hadoop2.x不支持jdk-8的下…

掌握 JavaScript 数组方法:了解如何操作和优化数组

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

Android Logcat 命令行工具

关于作者&#xff1a;CSDN内容合伙人、技术专家&#xff0c; 从零开始做日活千万级APP。 专注于分享各领域原创系列文章 &#xff0c;擅长java后端、移动开发、商业变现、人工智能等&#xff0c;希望大家多多支持。 目录 一、导读二、概览三、日常用法3.1 面板介绍3.2 日志过滤…

零代码编程:用ChatGPT批量将多个文件夹中的视频转为音频

有多个文件夹中的 视频&#xff0c;都要批量转换成音频格式。 转换完成后要删除视频。虽然现在已经有很多格式转换软件可以实现这个功能&#xff0c;但是需要一个个文件夹的操作&#xff0c;还要手动去删除视频。用ChatGPT来写一个批量自动操作程序吧&#xff1a; 输入提示词如…

获取el-select选中的下标

accountZbList:[ ]:下拉列表已通过接口获取数据 <el-row><el-col :span"12"><el-form-item label"账簙" prop"accountTook" class"itemzb"><el-select v-model"tableForm.accountTook" placeholder&…

软件测试基础学习

注意&#xff1a; 各位同学们&#xff0c;今年本人求职目前遇到的情况大体是这样了&#xff0c;开发太卷&#xff0c;学历高的话优势非常的大&#xff0c;公司会根据实际情况考虑是否值得培养&#xff08;哪怕技术差一点&#xff09;&#xff1b;学历稍微低一些但是技术熟练的…

改进的最大内切圆算法求裂缝轮廓宽度

前段时间我将网上最大内切圆算法进行了代码的整理&#xff0c;原先博主上传的代码稍微有点乱&#xff0c;可能也是它自己使用&#xff0c;大家可以看这篇整理好的&#xff1a;最大内切圆算法计算裂缝宽度。 最大内切圆算法详解 一个圆与给定的多边形或曲线的每一条边或曲线都…

13.(开发工具篇github)如何在GitHub上上传本地项目

一:创建GitHub账户并安装Git 二:创建一个新的仓库(repository) 三、拉取代码 git clone https://github.com/ainier-max/myboot.git git clone git@github.com:ainier-max/myboot.git四、拷贝代码到拉取后的工程 五、上传代码 (1)添加所有文件到暂存

PyTorch 模型性能分析和优化 — 第 1 部分

一、说明 这篇文章的重点将是GPU上的PyTorch培训。更具体地说&#xff0c;我们将专注于 PyTorch 的内置性能分析器 PyTorch Profiler&#xff0c;以及查看其结果的方法之一&#xff0c;即 PyTorch Profiler TensorBoard 插件。 二、深度框架 训练深度学习模型&#xff0c;尤其是…

江西广电会展集团总经理李悦一行莅临拓世科技集团调研参观,科技璀璨AIGC掀新潮

在江西这片充满活力的土地上&#xff0c;数字经济如潮水般涌动&#xff0c;会展文化与科技的完美结合&#xff0c;正如新时代的璀璨繁星照亮夜空&#xff0c;更预示着一场AIGC创新的壮丽篇章即将展开。作为拓世科技集团的老朋友&#xff0c;江西广电多位领导多次莅临拓世科技集…

【MATLAB源码-第40期】基于matlab的D*(Dstar)算法栅格路径规划仿真。

1、算法描述 D*算法路径规划 D*算法&#xff08;Dynamic A*&#xff09;是A*算法的一种变种&#xff0c;主要用于在地图中的障碍物信息发生变化时重新计算路径&#xff0c;而不需要从头开始。该算法适用于那些只有部分信息已知的环境中。 工作原理&#xff1a; 1. D*算法首先…

嵌入式Linux应用开发-第七章-RK3288和 RK3399的 LED驱动程序

嵌入式Linux应用开发-第七章-RK3288和 RK3399的 LED驱动程序 RK3288和 RK3399的 LED驱动程序7.3 RK3288和 RK3399的 LED驱动程序7.3.1 原理图7.3.1.1 fireflye RK3288的 LED原理图7.3.1.2 firefly RK3399的 LED原理图 7.3.2 所涉及的寄存器操作7.3.2.1 RK3288的 GPIO8_A1引脚7.…

探索ClickHouse——使用MaterializedView存储kafka传递的数据

在《探索ClickHouse——连接Kafka和Clickhouse》中&#xff0c;我们讲解了如何使用kafka engin连接kafka&#xff0c;并读取topic中的数据。但是遇到了一个问题&#xff0c;就是数据只能读取一次&#xff0c;即使后面还有新数据发送到该topic&#xff0c;该表也读不出来。 为了…