CR-NeRF 代码eval.py解析

news2025/1/11 2:28:07

        这段代码是一个用于CR-NeRF(Neural Radiance Fields)模型的推理脚本。它主要用于生成和保存渲染的图像,并计算图像质量的评价指标(如PSNR和SSIM)。以下是对这段代码的详细解析:

(1)导入了所需的库和模块

包括PyTorch、NumPy、tqdm(用于进度条)、imageio(用于图像保存)、以及其他自定义模块和函数。

import torch
import os
import numpy as np
from collections import defaultdict
from tqdm import tqdm
import imageio
from argparse import ArgumentParser

from models.rendering import render_rays_cross_ray
from models.nerf import *
from models.nerf_decoder_stylenerf import get_renderer
from utils import load_ckpt
import metrics
from einops import rearrange
from datasets import dataset_dict
from datasets.depth_utils import *
from models.linearStyleTransfer import encoder3, encoder_sameoutputsize
from models.networks import E_attr
from math import sqrt
import math
import json
from PIL import Image
from torchvision import transforms as T
from opt import get_opts
from train_mask_grid_sample import get_model
torch.backends.cudnn.benchmark = True

(2)定义函数 ​

   batched_inference(), 这个函数用于对光线进行批量推理。它将光线分成小块进行处理,以避免内存不足的问题。

from collections import defaultdict
import torch

def batched_inference(models, embeddings,
                      rays, ts, N_samples, N_importance, use_disp,
                      chunk,
                      white_back,
                      **kwargs):
    """
    对光线进行批量推理。

    参数:
    models: 包含模型(如粗略模型和精细模型)的字典。
    embeddings: 包含位置和方向嵌入的字典。
    rays: 光线数据,形状为 [B, 6],其中 B 是光线的数量。
    ts: 时间戳数据,形状为 [B]。
    N_samples: 每条光线的样本数量。
    N_importance: 重要性采样的样本数量。
    use_disp: 是否使用视差。
    chunk: 每个小块的大小。
    white_back: 背景是否为白色。
    **kwargs: 其他关键字参数。

    返回:
    包含渲染结果的字典。
    """
    B = rays.shape[0]  # 光线的总数
    results = defaultdict(list)  # 用于存储每个键的结果列表

    # 循环处理每个小块的光线
    for i in range(0, B, chunk):
        rendered_ray_chunks = \
            render_rays_cross_ray(models,
                        embeddings,
                        rays[i:i+chunk],  # 当前小块的光线
                        ts[i:i+chunk] if ts is not None else None,  # 当前小块的时间戳
                        N_samples,
                        use_disp,
                        0,
                        0,
                        N_importance,
                        chunk,
                        white_back,
                        test_time=True,
                        **kwargs)

        # 将渲染结果中的每个键值对添加到 results 字典中
        for k, v in rendered_ray_chunks.items():
            results[k] += [v]

    # 将 results 字典中的每个键的结果列表合并成一个张量
    for k, v in results.items():
        results[k] = torch.cat(v, 0)

    return results  # 返回合并后的结果字典

定义函数eulerAnglesToRotationMatrix(),这个函数用于将欧拉角转换为旋转矩阵。

def eulerAnglesToRotationMatrix(theta):
    R_x = np.array([[1,         0,                  0                   ],
                    [0,         math.cos(theta[0]), -math.sin(theta[0]) ],
                    [0,         math.sin(theta[0]), math.cos(theta[0])  ]
                    ])
    R_y = np.array([[math.cos(theta[1]),    0,      math.sin(theta[1])  ],
                    [0,                     1,      0                   ],
                    [-math.sin(theta[1]),   0,      math.cos(theta[1])  ]
                    ])
                
    R_z = np.array([[math.cos(theta[2]),    -math.sin(theta[2]),    0],
                    [math.sin(theta[2]),    math.cos(theta[2]),     0],
                    [0,                     0,                      1]
                    ])
    R = np.dot(R_z, np.dot( R_y, R_x ))
    return R

(3)主程序

这段代码是主程序的开始部分,主要负责初始化参数、加载数据集、定义嵌入和编码器等。

if __name__ == "__main__":
    # 检查是否是主程序入口
    args = get_opts()
    # 获取命令行参数,存储在args对象中

    kwargs = {'root_dir': args.root_dir, 'split': args.split}
    # 初始化关键字参数字典,包含根目录和数据集分割信息

    if args.dataset_name == 'blender':
        # 如果数据集名称是'blender'
        kwargs['img_wh'] = tuple(args.img_wh)
        # 添加图像宽度和高度到关键字参数字典
    else:
        # 否则
        kwargs['img_downscale'] = args.img_downscale
        kwargs['use_cache'] = args.use_cache
        # 添加图像降采样因子和是否使用缓存到关键字参数字典

    dataset = dataset_dict[args.dataset_name](args=args, **kwargs)
    # 根据数据集名称初始化数据集对象

    scene = os.path.basename(args.root_dir.strip('/'))
    # 获取场景名称,即根目录的最后一个部分

    embedding_xyz = PosEmbedding(args.N_emb_xyz-1, args.N_emb_xyz)
    embedding_dir = PosEmbedding(args.N_emb_dir-1, args.N_emb_dir)
    # 初始化位置编码和方向编码对象

    embeddings = {'xyz': embedding_xyz, 'dir': embedding_dir}
    # 将位置编码和方向编码存储在字典中

    if args.encode_a:
        # 如果启用了外观编码
        enc_a = encoder_sameoutputsize(out_channel=args.nerf_out_dim).cuda()
        # 初始化外观编码器并将其移动到GPU

        load_ckpt(enc_a, args.ckpt_path, model_name='enc_a')
        # 从检查点文件加载外观编码器的权重

        kwargs = {}
        # 重置关键字参数字典

        if args.dataset_name == 'blender':
            # 如果数据集名称是'blender'
            with open(os.path.join(args.root_dir, f"transforms_train.json"), 'r') as f:
                meta_train = json.load(f)
            # 读取训练数据的变换信息

            frame = meta_train['frames'][0]
            # 获取第一帧的信息

            image_path = os.path.join(args.root_dir, f"{frame['file_path']}.png")
            # 构建图像文件路径

            img = Image.open(image_path)
            img = img.resize(args.img_wh, Image.LANCZOS)
            # 打开图像并调整大小

            toTensor = T.ToTensor()
            normalize = T.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5])
            # 初始化图像转换工具

            img = toTensor(img) # (4, h, w)
            img = img[:3, :, :]*img[-1:, :, :] + (1-img[-1:, :, :]) # blend A to RGB (3, h, w)
            # 将图像转换为Tensor并进行预处理

            whole_img = normalize(img).unsqueeze(0).cuda()
            # 归一化图像并将其移动到GPU

            kwargs['a_embedded_from_img'] = enc_a(whole_img)
            # 使用外观编码器对图像进行编码,并将结果存储在关键字参数字典中

(4)模型加载和初始化

这段代码加载了NeRF模型和解码器,并从checkpoints文件中恢复它们的权重。

models=get_model(args)
nerf_coarse=models['coarse']
nerf_fine=models['fine']
decoder=models['decoder']
load_ckpt(nerf_coarse, args.ckpt_path, model_name='nerf_coarse')
load_ckpt(nerf_fine, args.ckpt_path, model_name='nerf_fine')
load_ckpt(decoder, args.ckpt_path, model_name='decoder')

 (5)数据集预处理

        为不同场景中的场景进行特定的预处理,包括图像的读取、下采样、归一化以及相机姿态的生成。每个场景有其特定的处理逻辑以确保测试数据的一致性和合理性。

# 初始化保存图像和度量结果的列表
imgs, psnrs, ssims = [], [], []

# 设置结果保存目录并创建该目录
dir_name = os.path.join(args.save_dir, f'results/{args.dataset_name}/{args.scene_name}')
os.makedirs(dir_name, exist_ok=True)

# 设置 kwargs 参数
kwargs['args']=args

# 如果数据集是 phototourism 且数据划分为测试集,进行特定处理
if args.dataset_name == 'phototourism' and args.split == 'test':
    # 定义测试图像的宽度和高度
    dataset.test_img_w, dataset.test_img_h = args.img_wh
    
    # 计算焦距,定义相机内参 (fov=60 degrees)
    dataset.test_focal = dataset.test_img_w / 2 / np.tan(np.pi/6)
    dataset.test_K = np.array([
        [dataset.test_focal, 0, dataset.test_img_w / 2],
        [0, dataset.test_focal, dataset.test_img_h / 2],
        [0, 0, 1]
    ])
    
    # 根据不同的场景进行不同的处理
    if scene == 'brandenburg_gate':
        # 选择特定图像作为外观嵌入
        img = Image.open(os.path.join(args.root_dir, 'dense/images', dataset.image_paths[dataset.img_ids_train[314]])).convert('RGB')
        img_downscale = 8
        img_w, img_h = img.size
        img_w = img_w // img_downscale
        img_h = img_h // img_downscale
        img = img.resize((img_w, img_h), Image.LANCZOS)
        
        # 对图像进行归一化和转换为张量
        toTensor = T.ToTensor()
        normalize = T.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5])
        img = toTensor(img)
        whole_img = normalize(img).unsqueeze(0).cuda()
        kwargs['a_embedded_from_img'] = enc_a(whole_img)
        
        dataset.test_appearance_idx = 314
        N_frames = 30 * 8

        # 定义相机的轨迹变换参数
        dx1 = np.linspace(-0.25, 0.25, N_frames)
        dx2 = np.linspace(0.25, 0.38, N_frames - N_frames // 2)
        dx = np.concatenate((dx1, dx2))

        dy1 = np.linspace(0.05, -0.1, N_frames // 2)
        dy2 = np.linspace(-0.1, 0.05, N_frames - N_frames // 2)
        dy = np.concatenate((dy1, dy2))

        dz1 = np.linspace(0.1, 0.3, N_frames // 2)
        dz2 = np.linspace(0.3, 0.1, N_frames - N_frames // 2)
        dz = np.concatenate((dz1, dz2))

        theta_x1 = np.linspace(math.pi / 30, 0, N_frames // 2)
        theta_x2 = np.linspace(0, math.pi / 30, N_frames - N_frames // 2)
        theta_x = np.concatenate((theta_x1, theta_x2))

        theta_y = np.linspace(math.pi / 10, -math.pi / 10, N_frames)
        theta_z = np.linspace(0, 0, N_frames)

        # 复制初始的相机姿态并在每一帧上应用变换
        dataset.poses_test = np.tile(dataset.poses_dict[1123], (N_frames, 1, 1))
        for i in range(N_frames):
            dataset.poses_test[i, 0, 3] += dx[i]
            dataset.poses_test[i, 1, 3] += dy[i]
            dataset.poses_test[i, 2, 3] += dz[i]
            dataset.poses_test[i, :, :3] = np.dot(eulerAnglesToRotationMatrix([theta_x[i],theta_y[i],theta_z[i]]), dataset.poses_test[i, :, :3])
    
    elif scene == 'trevi_fountain':
        # 选择特定图像作为外观嵌入
        img = Image.open(os.path.join(args.root_dir, 'dense/images', dataset.image_paths[dataset.img_ids_train[1548]])).convert('RGB')
        img_downscale = 8
        img_w, img_h = img.size
        img_w = img_w // img_downscale
        img_h = img_h // img_downscale
        img = img.resize((img_w, img_h), Image.LANCZOS)
        
        # 对图像进行归一化和转换为张量
        toTensor = T.ToTensor()
        normalize = T.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5])
        img = toTensor(img)
        whole_img = normalize(img).unsqueeze(0).cuda()
        kwargs['a_embedded_from_img'] = enc_a(whole_img)

        dataset.test_appearance_idx = dataset.img_ids_train[1548]
        N_frames = 30 * 8

        # 定义相机的轨迹变换参数
        dx = np.linspace(-0.8, 0.7, N_frames)
        dy1 = np.linspace(-0., 0.05, N_frames // 2)
        dy2 = np.linspace(0.05, -0., N_frames - N_frames // 2)
        dy = np.concatenate((dy1, dy2))

        dz1 = np.linspace(0.4, 0.1, N_frames // 4)
        dz2 = np.linspace(0.1, 0.5, N_frames // 4)
        dz3 = np.linspace(0.5, 0.1, N_frames // 4)
        dz4 = np.linspace(0.1, 0.4, N_frames - 3 * (N_frames // 4))
        dz = np.concatenate((dz1, dz2, dz3, dz4))

        theta_x1 = np.linspace(-0, 0, N_frames // 2)
        theta_x2 = np.linspace(0, -0, N_frames - N_frames // 2)
        theta_x = np.concatenate((theta_x1, theta_x2))

        theta_y = np.linspace(math.pi / 6, -math.pi / 6, N_frames)
        theta_z = np.linspace(0, 0, N_frames)

        # 复制初始的相机姿态并在每一帧上应用变换
        dataset.poses_test = np.tile(dataset.poses_dict[dataset.img_ids_train[1548]], (N_frames, 1, 1))
        for i in range(N_frames):
            dataset.poses_test[i, 0, 3] += dx[i]
            dataset.poses_test[i, 1, 3] += dy[i]
            dataset.poses_test[i, 2, 3] += dz[i]
            dataset.poses_test[i, :, :3] = np.dot(eulerAnglesToRotationMatrix([theta_x[i],theta_y[i],theta_z[i]]), dataset.poses_test[i, :, :3])

    elif scene == 'sacre_coeur':
        # 选择特定图像作为外观嵌入
        img = Image.open(os.path.join(args.root_dir, 'dense/images', dataset.image_paths[dataset.img_ids_train[58]])).convert('RGB')
        img_downscale = 8
        img_w, img_h = img.size
        img_w = img_w // img_downscale
        img_h = img_h // img_downscale
        img = img.resize((img_w, img_h), Image.LANCZOS)
        
        # 对图像进行归一化和转换为张量
        toTensor = T.ToTensor()
        normalize = T.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5])
        img = toTensor(img)
        whole_img = normalize(img).unsqueeze(0).cuda()
        kwargs['a_embedded_from_img'] = enc_a(whole_img)

        dataset.test_appearance_idx = dataset.img_ids_train[58]
        N_frames = 30 * 8

        # 定义相机的轨迹变换参数
        dx = np.linspace(-2, 2, N_frames)
        dy1 = np.linspace(-0., 2, N_frames // 2)
        dy2 = np.linspace(2, -0., N_frames - N_frames // 2)
        dy = np.concatenate((dy1, dy2))

        dz1 = np.linspace(0, -3, N_frames // 2)
        dz2 = np.linspace(-3, 0, N_frames - N_frames // 2)
        dz = np.concatenate((dz1, dz2))

        theta_x1 = np.linspace(-0, 0, N_frames // 2)
        theta_x2 = np.linspace(0, -0, N_frames - N_frames // 2)
        theta_x = np.concatenate((

(6)渲染和保存图像

遍历数据集中的每个样本,使用NeRF模型进行渲染,并将渲染结果保存为图像文件

    # 遍历数据集:
    for i in tqdm(range(len(dataset))):
        #  使用 tqdm库创建一个进度条,遍历数据集中的每个样本。
        # 获取样本数据:
        sample = dataset[i]
        rays = sample['rays']
        ts = sample['ts']
        # 从数据集中获取当前样本的光线(rays)和时间戳(ts)。

        # 处理测试集和外观编码:
        if args.split == 'test_test' and args.encode_a:
            whole_img = sample['whole_img'].unsqueeze(0).cuda()
            whole_img=(whole_img+1)/2
            kwargs['a_embedded_from_img'] = enc_a(whole_img)
         # 如果当前是测试集并且启用了外观编码,则对整个图像进行处理并生成外观嵌入。

        # 进行批量推理:
        results = batched_inference(models, embeddings, rays.cuda(), ts.cuda(),
                                    args.N_samples, args.N_importance, args.use_disp,
                                    args.chunk,
                                    dataset.white_back,
                                    **kwargs)
        # 调用 batched_inference函数进行批量推理,获取渲染结果。

        # 处理图像尺寸:
        if args.dataset_name == 'blender':
            w, h = args.img_wh
        else:
            w, h = sample['img_wh']
        # 根据数据集类型获取图像的宽度和高度。

        # 处理特征:
        feature=results['feature_fine'] #torch.Size([699008, 4])
        print("using fine feature")
        lastdim=feature.size(-1)
        feature = rearrange(feature, 'n1 n3 -> n3 n1', n3=lastdim)
        feature = rearrange(feature, ' n3 (h w) ->  1 n3 h w',  h=int(h), w=int(w),n3=lastdim)  ##torch.Size([1, 64, 340, 514])

        # 从渲染结果中获取精细特征,并重新排列其形状以匹配解码器输入格式。

        # 解码特征并生成RGB图像:
        rgbs_pred=models['decoder'](feature, kwargs['a_embedded_from_img'])
        rgbs_pred=rearrange(rgbs_pred, ' 1 n1 h w ->  (h w) n1',  h=int(h), w=int(w),n1=3)
        results['rgb_fine']=rgbs_pred.cpu()
        
        #保存渲染图象
        img_pred = np.clip(results['rgb_fine'].view(h, w, 3).detach().numpy(), 0, 1)
        img_pred_ = (img_pred*255).astype(np.uint8)
        imgs += [img_pred_]
        imageio.imwrite(os.path.join(dir_name, f'{i:03d}.png'), img_pred_)
        print("image saving path",os.path.join(dir_name, f'{i:03d}.png'))

    # 将渲染的RGB图像转换为NumPy数组,并保存为PNG文件。同时,将图像添加到 imgs列表中。

    if args.dataset_name == 'blender' or \
      (args.dataset_name == 'phototourism' and args.split == 'test'):
        imageio.mimsave(os.path.join(dir_name, f'{args.scene_name}.{args.video_format}'),
                        imgs, fps=30)
    print('Done')

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

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

相关文章

【亚马逊云科技】有手就能做到的Amazon Lightsail快速建站

文章目录 前言一、为什么选择Amazon Lightsail二、创建账号与登录注册亚马逊账号登录控制台 三、创建Amazon Lightsail进入控制台创建实例实例配置查看实例查看网站定制页面 总结 前言 不论是个人名片还是官方网站都离不开网站建设工作。计算机技术经历漫长的发展,…

2024年如何将低质量视频变成高质量视频

创建低质量的视频对您没有好处,尤其是当您打算将这些视频上传到社交媒体帐户时。观众不喜欢观看模糊和低质量的视频,而这个东西没有意义,不会为你的内容增加价值。 那么,您应该如何确保您的社交媒体观众喜欢您的视频内容呢&#x…

企业内部知识库意义何在?怎么搭建?

引言 在知识经济时代,企业竞争力的核心日益转向知识的管理与应用能力。随着企业规模的扩大和业务复杂性的增加,如何高效地收集、整理、存储并分享内部知识,成为了企业持续发展和创新的关键。企业内部知识库应运而生,它不仅帮助企…

自定义注解+拦截器实现,对部分敏感字段的加解密

上一篇,我用的自定义注解AOP的方式,实现敏感字段的加解密,这一篇换个方案,这个方案相比一个方案,节省了一部分的性能开销 第一步:新建自定义注解 /*** 敏感信息类注解*/ Inherited Target({ElementType.TYPE}) Retention(RetentionPolicy.RUNTIME) public interface EnableSe…

tomcat session共享

1. 日志监控工具 安装 tar xf goaccess-1.4.tar.gz cd goaccess-1.4/ yum install GeoIP-devel-1.5.0-13.el7.x86_64.rpm yum install -y ncurses-devel.x86_64 ./configure --enable-utf8 --enable-geoiplegacy make make install2. 使用 goaccess /usr/local/nginx/logs/a…

java多线程(六)关键字Volatile可见性、有序性以及单个变量的原子性

volatile关键字 作用 volatile 是 Java 虚拟机提供的轻量级的同步机制,主要用来确保变量被线程安全地读取和写入。 当一个变量定义为 volatile 后,它具备以下特性: 可见性:确保不同线程对这个变量操作的可见性,即一…

自存实践本地访问 nginx放前端打包好的项目

nginx 部署前端项目_哔哩哔哩_bilibili 将打包好的dits文件放到 配置nginx.conf文件的location 启动命令 start nginx.exe 输入localhost即可访问打包好的项目 nginx的特点 1.静态资源 2.转发 设置代理转发请求 关闭nginx .\nginx.exe -s quit

Kubernetes-Pod调度基础

一.复制控制器(ReplicationController,RC) RC用来确保Pod副本数达到预期值,这样可以确保一个或多个同类Pod总是可用的。可以通过扩缩来增加或减少pod。 (1)示例: vim replicationcontroller-ng…

Codeforces Round 967 (Div. 2)

文章目录 A. Make All Equal题目描述思路代码 B. Generate Permutation题目描述思路代码 C. Guess The Tree题目描述思路代码 A. Make All Equal 题目描述 一个数组,最多实行n-1次,计算最少多少次可以变为同一个数 思路 计算重复次数最多的数&#x…

产业园区数字化转型:面对挑战,我们如何把握机遇加速前行?

在当今数字化的时代浪潮中,产业园区数字化转型已成为推动经济发展和提升竞争力的关键举措。然而,这一进程并非坦途,充满了各种挑战。 产业园区数字化转型面临着技术更新换代快的压力。新技术不断涌现,如物联网、人工智能、大数据…

Mybatis 速通秘籍 节省回顾知识点和学习成本

目录 一、MyBatis简介 1、MyBatis历史 2、MyBatis特性 3、MyBatis下载 4、和其它持久层技术对比 二、搭建MyBatis 1、开发环境 2、创建maven工程 a>打包方式 b>引入依赖 3、创建MyBatis的核心配置文件 4、创建mapper接口 5、创建MyBatis的映射文件 6、通过j…

python学习之路 - python的异常、模块与包

目录 一、python的异常、模块与包1、了解异常2、异常的捕获方法a、捕获基本异常b、捕获指定异常c、捕获多个异常d、捕获异常后的finally 3、异常的传递4、python模块a、定义b、基础语法c、使用方法d、补充 5、python包a、定义b、操作方法c、使用方法 6、安装第三方python包a、命…

MVC和三层架构

👉参考文章:mvc简介,mvc与三层架构的区别 一.MVC是什么? Model-View-Controller(MVC)是一种软件架构模式,是软件设计模式的体现 ,用于组织代码并分离关注点,广泛应用于…

GPS和桩号互转

文章目录 前言一、通过bigmap软件生成坐标信息csv二、Java实现1.CSV分隔2.计算2.1 读取gps_data.csv2.2 读取piles.csv2.3 进行线性插值2.4 返回值实体2.5 根据GPS坐标计算距离工具2.6 根据GPS坐标读取桩号2.7 根据桩号读取GPS坐标(根据距离计算,找到最近的桩号) 前…

短视频SDK解决方案,智能技术加持,提升创作效率

随着社交媒体、直播电商、在线教育等领域的蓬勃发展,短视频以其独特的魅力迅速崛起,成为内容创作与传播的新风口。为了助力企业和个人轻松拥抱视频化趋势,美摄科技匠心打造了一套高效、易用的短视频SDK解决方案,以“轻编辑&#x…

【高级IO-2】IO多路转接之Select(概念及代码实例)

文章目录 I/O 多路转接 之 Select1. 了解select2. select 函数原型① fd_set 结构② 详细理解参数(readfds为例) 3. 理解select的执行过程4. select代码实例:监视多个文件描述符5. Socket就绪条件6. select代码实例:多路复用服务器…

每日掌握一个科研插图·2D密度图|24-08-21

小罗碎碎念 在统计学和数据可视化领域,探索两个定量变量之间的关系是一种常见的需求。为了更深入地理解这种关系,我们可以使用多种图形表示方法,这些方法在本质上是对传统图形的扩展和变体。 散点图:这是最基本的图形&#xff0c…

什么是 JavaConfig?

什么是 JavaConfig? 💖The Begin💖点点关注,收藏不迷路💖 JavaConfig是Spring框架的一项创新,它允许开发者使用纯Java代码来配置Spring IoC容器,从而避免了繁琐的XML配置。这一特性带来了诸多优…

【微信小程序】导入项目

1.在微信开发工具中,点击【导入项目】 2.在打开的界面中执行2个步骤 1.找到要导入项目的路径2.AppID要改成自己的AppID 3.package.json包初始化【装包之前要确保有package.json文件】 1.在【资源管理器】空白处,点击鼠标右键,选择【】&am…

免费的真是太香了!Chainlit接入抖音 Coze AI知识库接口快速实现自定义用户聊天界面

前言 由于Coze 只提供了一个分享用的网页应用,网页访问地址没法自定义,虽然可以接入NextWeb/ChatGPT web/open webui等开源应用。但是如果我们想直接给客户应用,还需要客户去设置配置,里面还有很多我们不想展示给客户的东西怎么办…