【计算机紧耦合设计项目】虚拟形象智能语音助手2024.10.10-10.19 可视化实现+硬件组装工作总结

我们在2024年参加了校内的计算机紧耦合项目,目的是制作一个有虚拟形象的智能语音助手,我在该项目中主要负责可以看见的部分,包括可视化模型、3D建模打印、MMD动画制作等。

在这个过程中我写了三个工作总结:

【计算机紧耦合设计项目】虚拟形象智能语音助手2024.9.30-10.10 第一次3D打印工作总结-CSDN博客

【计算机紧耦合设计项目】虚拟形象智能语音助手2024.10.10-10.19 可视化实现+硬件组装工作总结-CSDN博客

【计算机紧耦合设计项目】虚拟形象智能语音助手2024.10.20-10.27 可视化工作结束-CSDN博客

以下为正文内容:

10.10 选择大体方向,准备开始制作可视化方面内容,即虚拟人物对传入设备的操作做出响应动作。

本项目可视化灵感来源:

没钱去看演唱会? 宿舍自己DIY。MIKU全息投影演示及制作过程分享_哔哩哔哩_bilibili

一、可视化实现

1.思路

  1. 寻找模型、工具软件
  2. 制作每个操作对应的动作动画
  3. 编程使得做出操作时播放对应动画

2.模型选择

模型来源于模之屋,本项目中采用的模型为初音ミクV4C,非常感谢作者@Kinsama

3.制作工具选择

搜索MMD制作教程,找到主要有两个主要制作途径:MikuMikuDance、Blender(配合MMD插件)

MikuMikuDance效果预览

图1、图2

效果如图1、图2所示,可以看出图像清晰简约,色彩明快,而且软件响应速度很快。

Blender效果预览

图3、图4

效果如图3、图4所示,体积感过于浓厚,色彩沉重,而且即使关了渲染,运行速度仍然偏慢。

在对比后选择使用MikuMikuDance作为角色动作制作的主要工具。

4.制作过程

10.10-10.11 初步探索

图5

如图5所示,直接导入了现成的角色模型、场景模型以及角色动作,逐渐理解MMD制作模式,不过我们需要的是不发光的黑色背景和自己制作的动作,因此在后续将动作和场景移除,仅保留人物模型。

10.11-10.12 制作了眨眼+站立呼吸动作

图6

如图6所示,把人物动作调整至站姿,动作加入了眨眼,但仍显得过于僵硬,而且在每一次开始运行动画模拟时头发会自然下垂,做不到动画循环衔接。

解决方案:

  1. 加入呼吸动作。
  2. 多次循环动作,直到头发稳定的时候再进行素材的选取。

图7

如图7所示,加入了呼吸动作,并且选取了多段呼吸中稳定的几段画面进行录制。动作僵硬问题有所好转,然而手部的浮动会导致头发的摇晃,且头发不属于刚体难以单独控制,因此做出的动画衔接处会有明显的残影。

解决方案:给头发在固定状态下于动画首尾添加同一个关键帧

10.17 pygame代码实现+抬手动作制作

图8

如图8所示,用pygame实现了窗口动画的播放(我尝试过moviepy,但那个实质上主要是用代码进行视频操作而非视频播放,没必要使用这么冗杂的库),采用其中的sprite来构建Action类作为不同动作的基本类型,并逐帧播放avi源视频转成的jpg序列。我重新制作了站立的动画使得首尾衔接上,并且增加了按空格倾听的逻辑,配套的抬手动作也制作完毕。

存在问题:

  • 人物与窗口大小不匹配
  • 有时人物行动逻辑出现卡壳问题

解决方案:

  • 重新设置窗口大小
  • 设置输入间隔时间,保证在动作结束后才能进行下一次输入

10.18 制作完倾听抬手、倾听呼吸、倾听放手动作以及开头动画

图9,倾听抬手、倾听呼吸、倾听放手动作动画

图10,开头动画

如图9所示,制作完了倾听抬手、倾听呼吸、倾听放手动作动画,并加入到代码中。

图10展示的是使用AE制作的简易片头。

存在问题:

  • 由于Action类过于简单,在引入新的动作时代码逻辑混乱,进而导致判断条件很难写正确
  • 如图11所示,代码大量存在外部直接调用类内部的值的问题

图11

解决方案:

  • 重写Action类,增加配套的函数与使用过程中至关重要的变量,进而重构代码(可以在“7.相关代码迭代:10.19 重写Action类,重新构建代码”处看到相关代码)

10.19 修改Action库,重构代码

见“7.相关代码迭代:10.19 重写Action类,重新构建代码”

5.在学习MikuMikuDance过程中参考的网站

我是按顺序从上往下学的,推荐最后两个网站在对MMD有一定了解的情况下再去看。

6.在学习AE过程中参考的网站

实际上我有一定的pr基础,再加上MikuMikuDance里大量的对于关键帧的运用,很快就能手搓一个简易的开头动画,所以没有太往后学。

7.相关代码迭代:

10.18 实现抬手的代码,逻辑混乱,存在外部直接调用类内部的值的问题

import pygame
import os

class Action(pygame.sprite.Sprite):
    def __init__(self, images):
        super().__init__()
        self.images = images
        self.image = self.images[0]
        self.current_index = 0
        self.rect = self.image.get_rect()

    def change_image(self, index):
        self.current_index = index
        self.image = self.images[index]

    def update(self):
        # 更新当前帧索引,实现循环播放
        self.current_index = (self.current_index + 1) % len(self.images)
        self.image = self.images[self.current_index]

# 初始化pygame
pygame.init()

# 创建游戏窗口
screen = pygame.display.set_mode((1700, 1000))

# 加载站立帧
frame_count = 157  # 假设有157帧
image_files = [f"眨眼_{i+3999}.jpg" for i in range(1, frame_count + 1)]
images = []
for file in image_files:
    images.append(pygame.image.load(file))


# 创建STAND对象
STAND = Action(images)

frame_count = 103  # 假设有157帧
image_files = [f"倾听_{i+3999}.jpg" for i in range(1, frame_count + 1)]
images = []
for file in image_files:
    images.append(pygame.image.load(file))

HEAR = Action(images)
# 设置窗口标题
pygame.display.set_caption("STAND Animation")

# 游戏主循环
running = True
clock = pygame.time.Clock()
open=0
last_key_check = pygame.time.get_ticks()
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

    # 更新精灵的图像帧

    current_time = pygame.time.get_ticks()
    if current_time - last_key_check >= 100 and (HEAR.current_index>=20 or open==0):
        keys = pygame.key.get_pressed()
        if keys[pygame.K_SPACE]:
            open=(1-open)
            if(open==0):
                STAND.current_index=0
                STAND.image=STAND.images[STAND.current_index]
            else:
                HEAR.current_index=0
                HEAR.image = HEAR.images[HEAR.current_index]
        last_key_check = current_time
    if(open==0):STAND.update()
    if(open==1):HEAR.update()
    # 绘制精灵
    screen.fill((0, 0, 0))  # 清屏
    if(open==0):screen.blit(STAND.image, (100, 100))  # 将精灵绘制在窗口中
    else:screen.blit(HEAR.image, (100, 100))
    pygame.display.flip()

    clock.tick(30)  # 控制帧率

pygame.quit()

10.19 重写Action类,重新构建代码

import pygame

class Action(pygame.sprite.Sprite):
    def __init__(self, title, frame_count,loop=False):
        super().__init__()
        image_files = [(title+f"_{i + 3999}.jpg") for i in range(1, frame_count + 1)]
        images = []
        for file in image_files:
            images.append(pygame.image.load(file))
        self.images = images
        self.image = self.images[0]
        self.current_index = 0
        self.rect = self.image.get_rect()
        self.playState = False   #是否正在播放
        self.frame_count = frame_count
        self.over = False  #是否到达视频最后节点(过)
        self.loop = loop

    def change_image(self, index):
        self.current_index = index
        self.image = self.images[index]

    def update(self):
        # 更新当前帧索引,实现循环播放
        self.playState = True
        if(self.loop==True):
            self.current_index = (self.current_index + 1) % len(self.images)
        else:
            self.current_index = (self.current_index + 1)
            if(self.current_index+1 >=self.frame_count):
                self.playState = False
                self.over = True
        self.image = self.images[self.current_index]

    def isPlay(self):
        return self.playState

    def isOver(self):
        return self.over

    def draw(self):
        if(self.playState == False):
            self.current_index = 0
            self.image = self.images[self.current_index]
        self.update()
        screen.fill((0, 0, 0))  # 清屏
        screen.blit(self.image, (0, 0))  #画图

    def overIt(self):
        self.over=True  #相当于将进度条拉到结尾,应该是用于循环播放的

    def reviveIt(self):
        self.over=False  #相当于将进度条拉到开头
        # self.playState=False
# 初始化pygame
pygame.init()

# 创建游戏窗口
screen = pygame.display.set_mode((1920, 1080))

# 设置窗口标题
pygame.display.set_caption("MIKU Animation")

BEGIN = Action("开始动画",50)

#是否初始化
chushihua=False

# 游戏主循环
running = True
clock = pygame.time.Clock()
open=0   #open==1时为开启录音,此时应当起手;open==0时应当分两种情况(刚开始还未录音/收手)
last_key_check = pygame.time.get_ticks() - 150  #为了开局就能按空格



while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

    # 更新精灵的图像帧
    if not BEGIN.isOver():
        BEGIN.draw()
    else:
        if(chushihua==False):
            STAND = Action("眨眼", 157,loop=True)
            HEAR_START = Action("倾听起手", 31)
            HEAR_BREATH = Action("倾听呼吸", 216,loop=True)
            HEAR_END = Action("倾听收手", 64)
            chushihua=True
        else:  #已经播放完开头动画+初始化完毕

            #每次控制开关要有一定时间间隔,并且在播放抬放手动画的过程中,不允许操作
            current_time = pygame.time.get_ticks()
            if current_time - last_key_check >= 120 and (HEAR_START.isPlay()==False and HEAR_END.isPlay()==False):
                keys = pygame.key.get_pressed()
                if keys[pygame.K_SPACE]:
                    open = (1 - open)
                    #复活吧,我的倾听  #用于回复over,相当于把进度条调为0了
                    HEAR_START.reviveIt()
                    HEAR_BREATH.reviveIt()
                    HEAR_END.reviveIt()
                last_key_check = current_time

            #未在录音:刚开始还未录音/收手
            if(open == 0):
                if(HEAR_END.isOver()==False and HEAR_BREATH.isPlay()==True):
                    HEAR_BREATH.overIt()  #手动让它成为播完了的
                    HEAR_END.draw()
                else:
                    STAND.draw()
            #开启录音
            if(open == 1):
                if(HEAR_START.isOver()==False):  #没播完就接着播,播完了就往下播
                    HEAR_START.draw()
                elif(HEAR_BREATH.isOver()==False):  #循环的,你不overIt终结它,它是不会播完的,所以可以循环
                    HEAR_BREATH.draw()

    pygame.display.flip()
    clock.tick(30)  # 控制帧率

pygame.quit()

二、硬件组装工作

10.12 修改移动框架+组装打印好的3D移动结构+拆机箱+试图裁剪亚克力板

图12,图13,图14,考虑到圆柱足够细长,或许可以采用平铺的方式来打印,避免了不必要的支撑,打印成功

图15,由于挡板阻碍,恰好无法放下显示屏于移动框架上

图16,将两个挡板用美工刀去掉后得以正确放置显示屏

图17,组装并使用该活动结构

图18,拆除了机箱自带的按键底盘与接线

图19,图20,忙碌了一天的杨师傅终于白忙活了,对5mm厚亚克力板造成了5点伤害后不了了之

10.14 亚克力板加工完毕

图18,实验室的切割机割不开,后面找六楼的师傅割开的

感谢何同学。

三、题外话

模型网站最多下载的都是原神哈哈

MMD导出avi原画视频差点给我C盘干碎,转移到D盘去了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值