3DGS渐进式渲染 - 离线生成渲染视频

news2024/11/28 14:28:39

总览

输入:环绕Object拍摄的RGB视频
输出:自定义相机路径的渲染视频(包含渐变效果)

实现过程

首先,编译3DGS的C++代码,并跑通convert.py、train.py和render.py。教程如下:

  • github网址:https://github.com/graphdeco-inria/gaussian-splatting
  • 新手教程:https://www.youtube.com/watch?v=UXtuigy_wYc
  • 训练自己的视频数据-教程:https://www.youtube.com/watch?v=wuKhEGCt6ks
    在掌握训练自己的视频后,可以生成一组input图像对应的render图像,但点云和参数都是固定的,如:
  1. 渲染的scaling_modifier参数固定为1.0。
  2. 渲染时使用的点云 始终是train得到的完整点云。
    因此,我们为了得到上面视频中的渐变效果,需要调整这两个地方(scaling_modifier参数和点云采样)。

1 调整scaling_modifier参数

  1. 修改render.py中的调用的render函数,向里面传入scaling_modifier参数。
# 对每一帧进行渲染
for idx, view in enumerate(tqdm(views, desc="Rendering progress")):
    rendering = render(view, gaussians, pipeline, background, scaling_modifier=scaling_modifier)["render"]
  1. 进入该render函数,将scaling_modifier传入GaussianRasterizationSettings方法中。
def render(viewpoint_camera, pc : GaussianModel, pipe, bg_color : torch.Tensor, scaling_modifier = 1.0, override_color = None):

    # Create zero tensor. We will use it to make pytorch return gradients of the 2D (screen-space) means
    screenspace_points = torch.zeros_like(pc.get_xyz, dtype=pc.get_xyz.dtype, requires_grad=True, device="cuda") + 0
    try:
        screenspace_points.retain_grad()
    except:
        pass

    # Set up rasterization configuration
    tanfovx = math.tan(viewpoint_camera.FoVx * 0.5)
    tanfovy = math.tan(viewpoint_camera.FoVy * 0.5)

    raster_settings = GaussianRasterizationSettings(
        image_height=int(viewpoint_camera.image_height),
        image_width=int(viewpoint_camera.image_width),
        tanfovx=tanfovx,
        tanfovy=tanfovy,
        bg=bg_color,
        scale_modifier=scaling_modifier,
        viewmatrix=viewpoint_camera.world_view_transform,
        projmatrix=viewpoint_camera.full_proj_transform,
        sh_degree=pc.active_sh_degree,
        campos=viewpoint_camera.camera_center,
        prefiltered=False,
        debug=pipe.debug
    )

通过上面的方式,即可对每一帧在不同的scaling_modifier下进行渲染,该参数在SIBR Viewer中也可以修改,修改位置如下:
在这里插入图片描述
如下左图为scaling_modifier=0.01、右图为scaling_modifier=1.0
在这里插入图片描述

2 点云采样

为了实现视频一开始由中心物体向四周扩散的渐变效果,我们需要通过点云采样的方式,实现点云数量渐变式增多
具体步骤如下:

  1. 计算原始点云中所有点的密度大小。
  2. 以密度最大的点作为中心点,计算每个点到该点的距离,得到升序排序后的索引。
  3. 根据该索引生成渐变式的点云。
    对应在render.py中添加如下代码:
def get_indices(model_path, iteration):
    path = os.path.join(model_path, "point_cloud", "iteration_" + str(iteration), "point_cloud.ply")
    plydata = PlyData.read(path)
    xyz = np.stack((np.asarray(plydata['vertex']['x']),
                np.asarray(plydata['vertex']['y']),
                np.asarray(plydata['vertex']['z'])), axis=1)
    # 定义邻域半径
    neighbor_radius = 0.1  # 例如,这里假设邻域半径为0.1
    # 使用最近邻算法查找每个点的邻域内的点的数量
    nbrs = NearestNeighbors(radius=neighbor_radius, algorithm='auto').fit(xyz)
    densities = nbrs.radius_neighbors(xyz, return_distance=False)

    # 使用最近邻算法查找每个点的邻域内的点的数量
    nbrs = NearestNeighbors(radius=neighbor_radius, algorithm='auto').fit(xyz)
    densities = nbrs.radius_neighbors(xyz, return_distance=False)

    # 计算每个点的密度
    point_cloud_density = np.array([len(density) for density in densities])

    # 确定渲染顺序
    start_idx = np.argmax(point_cloud_density)
    start_point = xyz[start_idx]

    # 根据与起始点的距离对点云进行排序
    distances = np.linalg.norm(xyz - start_point, axis=1)
    sorted_indices = np.argsort(distances)

    return sorted_indices

在render_set函数中调用get_indices函数:

def render_set(model_path, name, iteration, views, gaussians, pipeline, background, scene):
    render_path = os.path.join(model_path, name, "ours_{}".format(iteration), "renders")
    gts_path = os.path.join(model_path, name, "ours_{}".format(iteration), "gt")

    makedirs(render_path, exist_ok=True)
    # makedirs(gts_path, exist_ok=True)

    ### 计算点的渲染顺序
    sorted_indices = get_indices(model_path, iteration)
    # 对给定的images.bin(相机外参)一帧帧图片进行渲染
    for idx, view in enumerate(tqdm(views, desc="Rendering progress")):
        # 修改点云切片
        if idx<120:
            indices = sorted_indices[:(len(sorted_indices)//120 * idx)]
            scene.change_pc_indice(indices=indices)
            scaling_modifier = 0.01
        elif scaling_modifier<1:
            scaling_modifier += 0.01
        else:
            scaling_modifier = 1
        rendering = render(view, gaussians, pipeline, background, scaling_modifier=scaling_modifier)["render"]
        torchvision.utils.save_image(rendering, os.path.join(render_path, '{0:05d}'.format(idx) + ".png"))

最后,运行render.py即可得到最后的渲染视频(包含渐变效果)。

3 自定义环绕Object的相机路径

render.py使用的相机外参和内参分别存储在images.bin和cameras.bin中。

cameras.bin(内参)

该文件解析(read_intrinsics_binary函数)后,得到如下key-value(int-Camera对象)组成的字典。

{...,
1: Camera(id=1, model='PINHOLE', width=1332, height=876, 
 params=array([1035.49659905, 1034.97186374,  666.  , 438.]))
 ,...}

images.bin(外参)

该文件解析(read_extrinsics_binary函数)后,得到如下key-value(int-Image对象)组成的字典

{...,
263: Image(id=263, qvec=array([-0.15935236, -0.46899572,  0.35922958,  0.79095129]), 
                   tvec=array([-0.68604342, -0.24766367,  1.17531395]), 
                   camera_id=1, name='IMG_6597.jpg', 
                 xys=array([[ 826.85421273,    3.56521302],
                           [ 791.22610197,    6.24990826],
                           [1318.28015465,    6.96729477],
                           ...,
                           [1041.33873779,  316.22219915],
                           [ 737.99930832,  487.77142606],
                           [ 649.78058365,   72.14452395]]), 
                 point3D_ids=array([   -1,    -1, 75770, ...,    -1,    -1,   -1]))
,...}

在不考虑测试集的时候,我们不会使用该字典的xys和point3D_ids,相机外参仅由qvec和tvec构成。

修改images.bin(外参)

为了生成自定义的相机路径,我们仅需修改images.bin中每一帧的qvec和tvec。核心代码如下:

# 读取相机内外参
images = read_extrinsics_binary('../C4879_4/sparse/0/images_original.bin')
qvecs, tvecs = get_qvec_tvec('../C4879_4/sparse/0/images_original.bin')  # 获取qvecs, tvecs

qvecs = np.array(qvecs)
tvecs = np.array(tvecs)
mean_x = tvecs[:,0].sum() / len(tvecs)
mean_y = tvecs[:,1].sum() / len(tvecs)
mean_z = tvecs[:,2].sum() / len(tvecs)
print(mean_x,mean_y,mean_z)
#################################以二维平面中的一个圆的轨迹为例############################
# 定义圆形轨迹的参数
radius = 1.0  # 圆的半径
num_poses = len(qvecs)  # 生成的外参数量
center = np.array([mean_x,mean_y,mean_z])  # 圆心坐标

# 生成沿着圆形轨迹的外参
poses = []
for i in range(num_poses):
    angle = 2 * np.pi * i / num_poses  # 在圆上均匀分布的角度
    position = center + np.array([radius * np.cos(angle), radius * np.sin(angle), 0])  # 根据角度计算位置
    q = R.from_euler('xyz', [0, angle, 0]).as_quat()  # 根据角度计算旋转四元数
    tvec = position  # 平移向量即为位置
    poses.append((q, tvec))

new_images = {}
for i in range(len(images)):
    new_images[i+1] = Image(id=images[i+1].id, qvec=np.array(poses[i][0]), tvec=np.array(poses[i][1]),
                          camera_id=images[i+1].camera_id, name='{:03d}'.format(i), xys=images[i+1].xys, 
                          point3D_ids=images[i+1].point3D_ids)

# 写入相机内外参
write_images_binary(new_images, '../C4879_4/sparse/0/images.bin')

使用到的依赖库和函数:

import numpy as np
import struct
import collections
from PIL import Image
from scipy.spatial.transform import Rotation
import pandas as pd
from scipy.spatial.transform import Rotation as R

CameraModel = collections.namedtuple(
    "CameraModel", ["model_id", "model_name", "num_params"])
Camera = collections.namedtuple(
    "Camera", ["id", "model", "width", "height", "params"])
BaseImage = collections.namedtuple(
    "Image", ["id", "qvec", "tvec", "camera_id", "name", "xys", "point3D_ids"])
Point3D = collections.namedtuple(
    "Point3D", ["id", "xyz", "rgb", "error", "image_ids", "point2D_idxs"])
CAMERA_MODELS = {
    CameraModel(model_id=0, model_name="SIMPLE_PINHOLE", num_params=3),
    CameraModel(model_id=1, model_name="PINHOLE", num_params=4),
    CameraModel(model_id=2, model_name="SIMPLE_RADIAL", num_params=4),
    CameraModel(model_id=3, model_name="RADIAL", num_params=5),
    CameraModel(model_id=4, model_name="OPENCV", num_params=8),
    CameraModel(model_id=5, model_name="OPENCV_FISHEYE", num_params=8),
    CameraModel(model_id=6, model_name="FULL_OPENCV", num_params=12),
    CameraModel(model_id=7, model_name="FOV", num_params=5),
    CameraModel(model_id=8, model_name="SIMPLE_RADIAL_FISHEYE", num_params=4),
    CameraModel(model_id=9, model_name="RADIAL_FISHEYE", num_params=5),
    CameraModel(model_id=10, model_name="THIN_PRISM_FISHEYE", num_params=12)
}
CAMERA_MODEL_IDS = dict([(camera_model.model_id, camera_model)
                         for camera_model in CAMERA_MODELS])

def qvec2rotmat(qvec):
    return np.array([
        [1 - 2 * qvec[2]**2 - 2 * qvec[3]**2,
         2 * qvec[1] * qvec[2] - 2 * qvec[0] * qvec[3],
         2 * qvec[3] * qvec[1] + 2 * qvec[0] * qvec[2]],
        [2 * qvec[1] * qvec[2] + 2 * qvec[0] * qvec[3],
         1 - 2 * qvec[1]**2 - 2 * qvec[3]**2,
         2 * qvec[2] * qvec[3] - 2 * qvec[0] * qvec[1]],
        [2 * qvec[3] * qvec[1] - 2 * qvec[0] * qvec[2],
         2 * qvec[2] * qvec[3] + 2 * qvec[0] * qvec[1],
         1 - 2 * qvec[1]**2 - 2 * qvec[2]**2]])

def rotmat2qvec(R):
    Rxx, Ryx, Rzx, Rxy, Ryy, Rzy, Rxz, Ryz, Rzz = R.flat
    K = np.array([
        [Rxx - Ryy - Rzz, 0, 0, 0],
        [Ryx + Rxy, Ryy - Rxx - Rzz, 0, 0],
        [Rzx + Rxz, Rzy + Ryz, Rzz - Rxx - Ryy, 0],
        [Ryz - Rzy, Rzx - Rxz, Rxy - Ryx, Rxx + Ryy + Rzz]]) / 3.0
    eigvals, eigvecs = np.linalg.eigh(K)
    qvec = eigvecs[[3, 0, 1, 2], np.argmax(eigvals)]
    if qvec[0] < 0:
        qvec *= -1
    return qvec

class Image(BaseImage):
    def qvec2rotmat(self):
        return qvec2rotmat(self.qvec)

def read_next_bytes(fid, num_bytes, format_char_sequence, endian_character="<"):
    """Read and unpack the next bytes from a binary file.
    :param fid:
    :param num_bytes: Sum of combination of {2, 4, 8}, e.g. 2, 6, 16, 30, etc.
    :param format_char_sequence: List of {c, e, f, d, h, H, i, I, l, L, q, Q}.
    :param endian_character: Any of {@, =, <, >, !}
    :return: Tuple of read and unpacked values.
    """
    data = fid.read(num_bytes)
    return struct.unpack(endian_character + format_char_sequence, data)

def read_extrinsics_binary(path_to_model_file):
    """
    see: src/base/reconstruction.cc
        void Reconstruction::ReadImagesBinary(const std::string& path)
        void Reconstruction::WriteImagesBinary(const std::string& path)
    """
    images = {}
    with open(path_to_model_file, "rb") as fid:
        num_reg_images = read_next_bytes(fid, 8, "Q")[0]
        for i in range(num_reg_images):
            binary_image_properties = read_next_bytes(
                fid, num_bytes=64, format_char_sequence="idddddddi")
            image_id = binary_image_properties[0]
            qvec = np.array(binary_image_properties[1:5])
            tvec = np.array(binary_image_properties[5:8])
            camera_id = binary_image_properties[8]
            image_name = ""
            current_char = read_next_bytes(fid, 1, "c")[0]
            while current_char != b"\x00":   # look for the ASCII 0 entry
                image_name += current_char.decode("utf-8")
                current_char = read_next_bytes(fid, 1, "c")[0]
            num_points2D = read_next_bytes(fid, num_bytes=8,
                                           format_char_sequence="Q")[0]
            x_y_id_s = read_next_bytes(fid, num_bytes=24*num_points2D,
                                       format_char_sequence="ddq"*num_points2D)
            xys = np.column_stack([tuple(map(float, x_y_id_s[0::3])),
                                   tuple(map(float, x_y_id_s[1::3]))])
            point3D_ids = np.array(tuple(map(int, x_y_id_s[2::3])))
            images[image_id] = Image(
                id=image_id, qvec=qvec, tvec=tvec,
                camera_id=camera_id, name=image_name,
                xys=xys, point3D_ids=point3D_ids)
            # if i>3:
            #     break
    return images

def write_next_bytes(fid, data, format_char_sequence, endian_character="<"):
    """pack and write to a binary file.
    :param fid:
    :param data: data to send, if multiple elements are sent at the same time,
    they should be encapsuled either in a list or a tuple
    :param format_char_sequence: List of {c, e, f, d, h, H, i, I, l, L, q, Q}.
    should be the same length as the data list or tuple
    :param endian_character: Any of {@, =, <, >, !}
    """
    if isinstance(data, (list, tuple)):
        bytes = struct.pack(endian_character + format_char_sequence, *data)
    else:
        bytes = struct.pack(endian_character + format_char_sequence, data)
    fid.write(bytes)

def write_images_binary(images, path_to_model_file):
    """
    see: src/colmap/scene/reconstruction.cc
        void Reconstruction::ReadImagesBinary(const std::string& path)
        void Reconstruction::WriteImagesBinary(const std::string& path)
    """
    with open(path_to_model_file, "wb") as fid:
        write_next_bytes(fid, len(images), "Q")
        for i, img in images.items():
            write_next_bytes(fid, img.id, "i")
            tmp_qvec = [q*1.01 for q in img.qvec.tolist()]
            write_next_bytes(fid, tmp_qvec, "dddd")
            tmp_tvec = [v*1.02 for v in img.tvec.tolist()]
            write_next_bytes(fid, tmp_tvec, "ddd")
            write_next_bytes(fid, img.camera_id, "i")
            for char in img.name:
                write_next_bytes(fid, char.encode("utf-8"), "c")
            write_next_bytes(fid, b"\x00", "c")
            write_next_bytes(fid, len(img.point3D_ids), "Q")
            for xy, p3d_id in zip(np.zeros_like(img.xys), np.zeros_like(img.point3D_ids)):
                write_next_bytes(fid, [*xy, p3d_id], "ddq")

def get_qvec_tvec(path_to_model_file):
    qvecs = []
    tvecs = []
    with open(path_to_model_file, "rb") as fid:
        num_reg_images = read_next_bytes(fid, 8, "Q")[0]
        for i in range(num_reg_images):
            binary_image_properties = read_next_bytes(
                fid, num_bytes=64, format_char_sequence="idddddddi")
            image_id = binary_image_properties[0]
            qvec = np.array(binary_image_properties[1:5])
            qvecs.append(qvec)
            tvec = np.array(binary_image_properties[5:8])
            tvecs.append(tvec)
            camera_id = binary_image_properties[8]
            image_name = ""
            current_char = read_next_bytes(fid, 1, "c")[0]
            while current_char != b"\x00":   # look for the ASCII 0 entry
                image_name += current_char.decode("utf-8")
                current_char = read_next_bytes(fid, 1, "c")[0]
            num_points2D = read_next_bytes(fid, num_bytes=8,
                                           format_char_sequence="Q")[0]
            x_y_id_s = read_next_bytes(fid, num_bytes=24*num_points2D,
                                       format_char_sequence="ddq"*num_points2D)
            xys = np.column_stack([tuple(map(float, x_y_id_s[0::3])),
                                   tuple(map(float, x_y_id_s[1::3]))])
            point3D_ids = np.array(tuple(map(int, x_y_id_s[2::3])))
    return qvecs, tvecs

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

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

相关文章

如何安装 IntelliJ IDEA 最新版本——详细教程

IntelliJ IDEA 简称 IDEA&#xff0c;被业界公认为最好的 Java 集成开发工具&#xff0c;尤其在智能代码助手、代码自动提示、代码重构、代码版本管理(Git、SVN、Maven)、单元测试、代码分析等方面有着亮眼的发挥。IDEA 产于捷克&#xff0c;开发人员以严谨著称的东欧程序员为主…

利用大语言模型,矢量数据库实现数据库的智能搜索

目的 数据库使用SQL 语言查询数据&#xff0c;数据库的记录中要有一个关键字段&#xff08;通常称为主键字段&#xff0c;它的值在数据库列表中是唯一的&#xff09;,数据记录是结构化的. 如果你需要根据数据记录的内容来查询数据记录&#xff0c;就需要通过Select 语句在数据库…

SQL255 给出employees表中排名为奇数行的first_name

题目来源&#xff1a; 给出employees表中排名为奇数行的first_name_牛客题霸_牛客网 描述 对于employees表中&#xff0c;输出first_name排名(按first_name升序排序)为奇数的first_name CREATE TABLE employees ( emp_no int(11) NOT NULL, birth_date date NOT NULL, firs…

春藤实业启动SAP S/4HANA Cloud Public Edition项目,与工博科技携手数字化转型之路

3月11日&#xff0c;广东省春藤实业有限公司&#xff08;以下简称“春藤实业”&#xff09;SAP S/4HANA Cloud Public Edition&#xff08;以下简称“SAP ERP公有云”&#xff09;项目正式启动。春藤实业董事长陈董、联络协调项目经理慕总、内部推行项目经理陈总以及工博董事长…

【.Net动态Web API】背景与实现原理

&#x1f680;前言 本文是《.Net Core进阶编程课程》教程专栏的导航站&#xff08;点击链接&#xff0c;跳转到专栏主页&#xff0c;欢迎订阅&#xff0c;持续更新…&#xff09; 专栏介绍&#xff1a;通过源码实例来讲解Asp.Net Core进阶知识点&#xff0c;让大家完全掌握每一…

pt-archiver归档表数据

一 介绍 pt-archiver的原理主要是根据定义的时间间隔(sleep参数)&#xff0c;扫描要清理的数据表。它按照指定的规则分批(limit参数)将查询到的记录转移到其他表或文件中&#xff0c;发现它是按主键去删除的表数据&#xff0c;对数据库影响很小。 二 语法 /bin/pt-archiver …

关于外网java后端服务访问内网minio中间件,因连接minio超时,启动失败问题

注&#xff1a;服务器情况&#xff1a;2台服务器&#xff0c;内网服务器包含&#xff08;activemq、minio、nginx、redis、mysql、后端java服务&#xff09;。外网服务器只有后端java服务&#xff0c;访问内网的中间件&#xff08;内网服务器开放了部分指定端口&#xff09; 问…

GPT状态和原理 - 解密OpenAI模型训练

目录 1 如何训练 GPT 助手 1.1 第一阶段 Pretraining 预训练 1.2 第二阶段&#xff1a;Supervised Finetuning有监督微调 1.3 第三阶段 Reward Modeling 奖励建模 1.4 第四阶段 Reinforcement Learning 强化学习 1.5 总结 2 第二部分&#xff1a;如何有效的应用在您的应…

中文编程入门(Lua5.4.6中文版)第十二章 Lua 协程 参考《愿神》游戏

在《愿神》的提瓦特大陆上&#xff0c;每一位冒险者都拥有自己的独特力量——“神之眼”&#xff0c;他们借助元素之力探索广袤的世界&#xff0c;解决谜题&#xff0c;战胜敌人。而在提瓦特的科技树中&#xff0c;存在着一项名为“协同程序”的高级秘术&#xff0c;它使冒险者…

Go 语言中的 GIF 图像处理完全指南:`image/gif`的技术与实践

Go 语言中的 GIF 图像处理完全指南&#xff1a;image/gif的技术与实践 概述安装与基础设置导入 image/gif 包初步配置示例&#xff1a;设置一个简单的 GIF 编码环境 读取与解码 GIF 图像读取 GIF 文件解析 GIF 数据 创建与编码 GIF 图像创建 GIF 图像编码 GIF 图像 处理 GIF 动…

利用 Python 开发手机 App 实战

Python语言虽然很万能&#xff0c;但用它来开发app还是显得有点不对路&#xff0c;因此用Python开发的app应当是作为编码练习、或者自娱自乐所用&#xff0c;加上目前这方面的模块还不是特别成熟&#xff0c;bug比较多&#xff0c;总而言之&#xff0c;劝君莫轻入。 准备工作 …

BGP边界网关路由实验(华为)

一&#xff0c;技术简介 BGP&#xff08;边界网关路由协议&#xff09;是一种自治系统&#xff08;AS&#xff09;间的协议&#xff0c;主要用于在不同的AS之间交换路由信息。AS是一个由一组网络设备和路由器组成的网络集合&#xff0c;这些设备可以在一个共同的管理域中协同工…

通过本机电脑远程访问路由器loopback的ip

实验拓扑图 本机电脑增加路由信息 正常设置telnet用户&#xff0c;然后通过本地电脑telnet 软件ensp中的设备&#xff0c;尝试是否可以正常访问即可 测试通过本地电脑可以正常访问ensp里面设备的loopback的ip地址了 最重要的一点是本机需要增加一条路由route add ip mask 下…

.NET 发布,部署和运行应用程序

.NET应用发布 发布.Net应用有很多种方式&#xff0c;下面列举三种发布方式&#xff1a; 单文件发布跨平台发布Docker发布 单文件发布 右键工程&#xff0c;选择“发布”&#xff0c;部署模式选择“独立”&#xff0c;目标运行时选择自己想要部署到的系统&#xff0c;我这里用…

GIT上超火的阿里内部1000页Java核心笔记,啃完竟然拿到阿里P7offer!

除了ReetrantLock&#xff0c;你还接触过JUC中的哪些并发工具&#xff1f; 请谈谈ReadWriteLock 和StampedLock。 如何让Java的线程彼此同步&#xff1f;你了解过哪些同步器&#xff1f;请分别介绍下。 CyclicBarrier和CountDownLatch看起来很相似&#xff0c;请对比下呢&am…

分布式限流——Redis + Lua脚本实现令牌桶算法

主要思路概括如下&#xff1a; 定义数据结构&#xff1a; 使用Redis存储令牌桶的状态&#xff0c;包括当前令牌数&#xff08;KEYS[1]&#xff09;和上一次令牌填充的时间戳&#xff08;KEYS[1]:last&#xff09;。 计算新增令牌&#xff1a; 获取当前系统时间与上次令牌填充时…

type-cDP输入转双type-cDP输出,加type-c接口充电管理同时接两台显示器或者VR投屏,龙迅LT8712SX方案,龙迅桥接芯片方案

type-c的应用在各种设备上更加广泛&#xff0c;包括手机&#xff0c;电脑&#xff0c;游戏掌机&#xff0c; 因为type-c的功能非常强大&#xff0c;可以做到PD快充&#xff0c;DP信号输出&#xff0c;USB信号输出&#xff0c;所以很多设备为了做得更简洁都开始把其他的如HDMI接…

Docker应用推荐个人服务器实用有趣的项目推荐

Wallabag&#xff1a;是一个开源的、自托管的文章阅读和保存工具。它允许你保存网页文章并进行离线阅读&#xff0c;去除广告和不必要的内容&#xff0c;以提供更好的阅读体验。Wallabag支持多种导入和导出格式&#xff0c;并提供了一些实用的功能&#xff0c;如标签、阅读列表…

小程序如何优化搜索排名,获取曝光

在移动互联网时代&#xff0c;小程序以其便捷、轻量级的特点&#xff0c;逐渐成为用户获取服务的重要渠道。然而&#xff0c;小程序数量众多&#xff0c;如何让自己的小程序在搜索中脱颖而出&#xff0c;获取更多的曝光和流量&#xff0c;成为众多开发者关注的焦点。 一、理解…

Spring AI【人工智能】

Spring AI【人工智能】 前言版权推荐Spring AI官网介绍使用新建项目配置pom.xml配置application.properties创建Controller 测试 最后 前言 2024-4-11 10:58:44 昨天晚上睡觉刷B站看到一个视频 以下内容源自《【人工智能】》 仅供学习交流使用 版权 禁止其他平台发布时删除…