基于Python实现看图说话和微表情识别【100010260】

1. 设计思想

对于人类来说,描述一张图片的内容是非常重要的。但因这个过程并没有标准答案,因此对于计算机来说这并不是一个简单地过程。我们希望通过本次实验能够设计一个模型完成让计算机给图片设定 caption 的目标。更进一步,如果在图片中检测到人脸,我们希望能识别出人的情绪表情。最终呈现出如图 3.1 的效果:

图 3.1 实现目标

2. 实验环境和工具

Jupyter Notebook:

。Tensorflow

。Keras

3. 实验过程

3.1 看图说话

3.1.1 数据集介绍

Flickr8k Dataset:该数据集已经成为研究基于句子的图片描述的基准,该数据集包括了 8052 张图片,每张图片包括了 5 句相关的描述性句子,示例如下:

图 1 数据集的示例

3.1.2 实验环境和工具

Jupyter Notebook:

。Tensorflow

。Keras

3.1.3 数据预处理

3.1.3.1 基本的数据清理

大写转换为小写,删除标点符号,去除单复数等,实现效果如图 4.1 所示:

图 4.1 原数据表示

图 4.2 处理后的数据表示

3.1.3.2 Unique words 的统计

将所有在描述语言中出现过的单词组成一个 vocabulary,统计在 vocabulary 中出现过的单词。起初计算出约 40000 个语句中总共出现 8763 个单词,但由于许多单词只出现两三次,对于预测性的模型来说,无实质性的帮助,因此接下来我们只考虑在所有语句中出现次数大于十的单词,计算出此时的 vocabulary 中就变为 1651 个单词。更进一步,我们还要多增加一个 0 padding,因此总单词数为 1652。可参考图 4.3 的流程:

图 4.3 Unique words 统计

3.1.3.3 特征向量的提取

运用 InceptionV3 模型将图片转换为一个固定长度(length=2018)的向量,使其可以作为输入到神经网路。

InceptionV3 原来是给图片分类的模型,由于我们的目标只是提取图片的特征向量,我们就移去了最后的 softmax 层,从倒数第二层中提取特征向量,如图 4.4 所示:

图 4.4 特征向量的提取

3.1.3.4 词编码

将每个 vocabulary 中的词编码为一个固定大小的向量,并创建两个 Pyhon 的 Dictionary,分别为 wordtoix[‘abc’]:返回’abc’的索引;ixtoword[k]:返回索引为“k“的单词,每个单词的索引为 1-1652 的其中一个整数。

3.1.3.5 计算长度

计算 caption 的最大长度:34

3.1.3.6 data matrix 的构建过程

(在此举一个例子以更好地阐释)

eg. 以两张训练图片一张测试图片组成,如图 4.5 所示:

图 4.5 实例

  • 将 image1、image2 转换为长度为 2048 地特征向量

  • 给清理过的 caption 加上头尾标志(startseq、endseq)

 Caption_1 -> “startseq the black cat sat on grass endseq”
Caption_2 -> “startseq the white cat is walking on road endseq”
vocab = {black, cat, endseq, grass, is, on, road, sat, startseq, the, walking, white}
  • 给 vocabulary 中的单词分配整数索引

black -1, cat -2, endseq -3, grass -4, is -5, on -6, road -7, sat -8, startseq -9, the -10, walking -11, white -12
  • 为了预测 caption 中的第 t+1 个单词,我们可以通过前 t 个单词组成的部分的 caption 和图片的特征向量来进行。预测 caption 从 startseq 开始直到 endseq 结束,如图 4.6 所示:

图 4.6 单词依次预测以组成 caption

将每个单词以索引来表示,效果如 4.7 所示:

图 4.7 将 partial caption 用索引表示

将 caption 补全为同一长度,统一的长度即为之前计算出的 caption 最大数 34,补全的元素为 0,即所谓的 0 padding,效果如 4.8 所示:

图 4.8 zero padding

3.1.4 模型搭建

图 5.1 模型的流程思路

如图 5.1 所示,我们希望以 partial caption 和图片的特征向量为输入,因此起初会有两个 tensor。首先 partial caption 经过预处理得到长度为 34 的向量后经过一个 embedding 层,把每个单词都映射到一个长度为 200 的向量,经过一层 Dropout 防止过拟合,之后经过一层 LSTM(选择 LSTM 的原因:LSTM 在自然语言的处理中能发挥不错的作用,并且相比普通的 RNN,LSTM 在更长的序列中有更好的表现)得到一个(batch_size,256)的输出。

同时,图片的特征向量经过一层 Dropout 防止过拟合,之后再经过一层全连接层同样得到一个(batch_size,256)的输出。

我们把两个格式相同的 tensor 合为一个,以便更好的训练得出最终结果,之后再经过一个全连接层后,经最后一层 softmax 层,产生涵盖 1652 个在 vocabulary 出现的单词的概率分布,基于 greedy search,概率分布最大的单词即我们要选择的输出单词。具体实例如图 5.2 所示

图 5.2 迭代的具体实现实例

迭代循环的终止条件有两个:

  • 以“endseq“结尾,模型认为 caption 已经完成

  • 句长大于 34,为了避免一直迭代下去,强制终止

3.1.5 模型的训练

我们训练这个模型设定了 epoch 为 30,前 20 个 epoch 的学习率设为 0.001,batch size 设为 3。当完成了 20 次迭代后,将学习率降为 0.0001 并且将 batch size 设为 6。用这些超参数的原因是因为当训练到达后半程时,模型逐渐趋向平缓,我们必须减小学习率才能在最低点边缩小步长,以趋近最低点。并且,适当的增加 batch size 使梯度的更新更加有效。

3.1.6 模型评估

本模型的预测结果使用 BLEU 进行预测。

BLEU 能作为机器翻译的一个评估指。它采用了 N-gram 的匹配规则,能够算出比较译文和参考译文之间 n 组词的相似的一个占比。随着 n-gram 的增大,总体的精度得分是呈指数下降的,所以一般 N-gram 最多取到 4-gram。

一般情况 1-gram 可以代表原文有多少词被单独翻译出来,可以反映译文的充分性,2-gram 以上可以反映译文的流畅性,它的值越高说明可读性越好。这两个指标是能够跟人工评价对标的。

图 5.3.1 1-gram 准确度(该例为 5/6)

图 5.3.2 2-gram 准确度(该例为 3/5)

图 5.3.3 3-gram 准确度(该例为 1/4)

N-gram 的一个弊端是其译文准确度的匹配关系不能很好地体现译文长度不准确的问题。因此,针对翻译译文长度比参考译文要短的情况,就需要一个惩罚的机制去控制。在此便引入了惩罚因子的概念。惩罚因子的计算公式如下:

图 5.4 惩罚因子 BP 的计算

C 是测试译文的词数,r 是参考译文的词数

BLEU 算法就是在这两个概念的基础上整合得到,其计算公式如图 5.5 所示,BLEU 值越大表示测试译文与参考译文越接近,反之则差别越大。

图 5.5 BLEU 算法的计算公式

经过分析,我们发现 BLEU 尽管在一定程度上可以作为测试出的 caption 和原 caption 的评估指标,也比较方便和快捷,但它无法考虑语法上的准确性,测评的精度也会收到常用词的干扰。同时 BLEU 无法考虑同义词或相似表达的情况,因此作为该实验的评估指标还是存在一定的缺陷。

3.1.7 测试结果

3.1.7.1 较理想的测试结果:

图 6.1 比较理想的测试结果

3.1.7.2 不太理想的测试结果

图6.2 不太理想的测试结果

3.1.7.3 结果分析:

针对实验结果,我们发现测试样例中有匹配度高的 caption,也有结果不太理想的测试结果,分析后我们认为:部分测试对图片中的数目、颜色或人物性别的检测出现差错,可能是特征提取的弊端造成的,因此未来优化时可以采取更好的特征提取模型(如 vgg 等);此外由于该数据集不够大,对模型的训练并不能达到很理想的效果,因此我们需要更大的数据集去训练它(实际操作中,我们有尝试用更大的数据集,但因为 CPU 的限制,最终没有跑出来);针对优化,我们还提出了可以调整超参数优化模型训练、用交叉验证集避免过拟合、并寻找比 BLEU 更好的检测结果的方法等。

3.2 微表情识别

3.2.1 数据集

3.2.1.1 来源

– Human Images Source-CK+: http://www.consortium.ri.cmu.edu/ckagree/

– Human Images Source-Kaggle Dataset: https://www.kaggle.com/jonathanoheix/face-expression-recognition-dataset

– Human Images Source-Jaffebase Dataset: http://www.kasrl.org/jaffe.html

– Animated Images Source-FERG_DB: https://grail.cs.washington.edu/projects/deepexpr/ferg-db.htmlhttps://grail.cs.washington.edu/projects/deepexpr/ferg-db.html)

3.2.1.2 特点展示

  • CK+ Dataset:

最左边的每个文件夹对应一个人;

中间的每个文件夹对应一个人的一组表情序列图片;

最右边的一组图片记录的是从面无表情到表情饱满的过程:见下图

  • Kaggle Dataset:已经划分好 train 和 validation 集合,见下图:

  • Jaffebase Dataset:文件夹中所有的图片都可以通过文件名字划分表情类别,见下图

  • FERG_DB Dataset:动漫表情,且图片已经按照表情类别分类,见下图:

3.2.2 实验思路

  • 目的:对输入的图像检测人脸,并且基于人类的七种基本表情,对输入图像的人脸进行情绪识别

  • 将人类和动漫表情划分为 7 种不同的类别,划分后每一种表情都有两个文件夹,分别人类和动漫表情,所以总计一共有 14 个文件夹;

  • 将每一个文件夹中的文件读入成为一个 DataFrame,总计 14 个 DataFrame,将所有人类表情的 DataFrame 结合起来,将所有的动漫表情的 DataFrame 结合起来,所有表情总计 1 万多张

  • 最后得到两个 DataFrame,一个是人类表情,一个是动漫表情。

  • 七种表情分别是:

-ANGER   -DISGUST   -FEAR   -HAPPY   -NEUTRAL    -SAD   -SURPRISE

3.2.3 实验过程

3.2.3.1 数据集准备

  • CK+ Dataset:

处理表情序列,从序列中选择有表情的图片。并运用linux 脚本将表情按照标签分为7类,因为ck+数据集下载下来每一个文件夹都有对应的表情标签,按照标签给每个表情文件夹分类,见下图

  • Jaffebase Dataset

从文件名中提取所属类别:

  • 准备后的结果:

  • 14 folders:7 human image folders and 7 animated image folders

  • 1w+ images in total

3.2.3.2 数据预处理

Crop and Resize

  • Convert to gray-scale

  • Detect face: OpenCV HAAR Cascade.

  • Crop the image to the face.

  • Resize the image to 350*350.

  • Save the image.

之所以将图片都转化为灰度是因为我们的图片一部分是灰度,一部分是彩色,而考虑到颜色并不影响表情的识别,所以为了保持一致性,我们将所有图片都转化为灰度图片。

过程见下图:

Train-Test-Split:

train:validation:test:5:2:3

对于人类和动漫表情图片,我们划分训练集-检验集-测试集

  • Human images distribution:

  • Animated images distribution:

处理好后得到6个set【3个为Human images的train-validation-test set,3个为Animated images的train-validation-test set】,将Human images的train set和Animated images 的train set合并为Combined train set,结果得到5个DataFrames。

其中 Combined train set 用于训练模型,Human images 的 Validation set 和 Animated images 的 Validation set 用于 cross validation(交叉检验)。

3.2.3.3 第一个模型:借助 VGG16

模型搭建

  • 使用pre-trained model的原因:

  • 对于图像处理我们没有足够的计算能力和足够的图片,卷积本身的运算时间花费不小,所以为了减少时间开销,我们决定 transfer learning。

  • 利用 Transfer learning 的概念,我们将其他 pre-trained model 得到的参数转移到我们的数据中,这样我们将数据传递给这些模型,提取图像的特征(bottleneck_features)。

  • VGG16 神经网络以数百万幅图像的数据集为基础进行了训练。VGG16 包含 16 层,其中 13 层是卷积层。我们利用 VGG16 作为 pre-trained model 生成 bottleneck_features。

  • 从VGG16中提取bottleneck_features

利用代码 model.predict(), 我们把图片一张一张传入 VGG16 模型,得到 bottleneck_features 并且将其作为 numpy.array 存储, 通过这种方式我们实现 transfer learning。

【只采用 VGG16 第 13 层前面的部分,第 12 层的输出作为 bottleneck_features】

  • 搭建模型 MLP

在 VGG16 提取图片 bottleneck_features 的基础上,我们将这些特征传递给 MLP(MLP 作为顶级模型),然后利用 MLP 减少损失函数的值,并且更新 MLP 和 CNN 中的权重。

# model architecture
def model(input_shape):model = Sequential()model.add(Dense(512, activation='relu', input_dim = input_shape))model.add(Dropout(0.1))model.add(Dense(256, activation='relu'))model.add(Dense(128, activation='relu'))model.add(BatchNormalization())model.add(Dense(64, activation='relu'))model.add(Dense(output_dim = no_of_classes, activation='softmax'))return model

【借助 MLP,通过 backpropagation 降低 lostfunction 的值】

搭建 5 层全连接层,所有层都是用 relu 作为激活函数,第一层包含 512 个激活单元,第二层包含 256 个激活单元,第三层包含 128 个激活单元,第四层包含 64 个激活单元,第五层包含 7 个 softmax 单元,softmax 是用于多分类的逻辑回归。

该模型会生成 7 个概率值,这些概率值的和为 1,所有结果会传递给 cross-entropy 损失函数。

可以注意到 dropout rate 非常小,起初我们尝试了 0.3,但是在 15 个 epochs 之后训练集和检验集的 loss 值不会减小。在渐渐尝试减小 dropout 的过程中,确定 dropout rate 为 0.1 时,训练集和检验集的 loss 值会降低,同时准确度会提升

  • 读取bottleneck_features, 放入MLP

模型训练

训练 20 个 eopchs,结果如下

Multi-Class Log-Loss

  • Training Loss: 0.70 to 0.04

  • CV Human loss: 2.25 to 0.05

  • CV Animated loss: 1.47 to 0.01

  • Train Accuracy: 75% to 99%

  • CV Human Accuracy: 44% to 86%

  • CV Animated Accuracy: 70% to 99%

模型评估

我们把人类和动漫表情图片分开,以便能够分别在这两个集合上面进行测验

  • Confusion Metric

Confusion and Recall Matrix Human Images

  • In confusion metric:

Biased on the “angry” class

  • In recall metric

Many images in “sad” class which are predicted in “angry”.

  • Conclusion

Model is not completely tell “angry” and “sad”

  • Confusion and Recall Matrix Animated Images

% accuracy.

Why?

The ratio of train images from animated to human is approximately 9:1.

Learning human features is hard as compared to animated images.

The size of face

expression angles

etc

未来展望

  • 微调 VGG16 的最后 2、3 层卷积层

  • 寻找有更多 variance 的图片

  • 图片尺寸大于 400*400

  • 一张图片中多个人脸时能够检测大部分人脸并识别情绪

3.2.3.4 模型二

和模型一使用相同的经过预处理后划分好 train-validation-test 集合的数据

模型搭建

from keras.layers import Dense, Input, Dropout, GlobalAveragePooling2D, Flatten, Conv2D, BatchNormalization, Activation, MaxPooling2D
from keras.models import Model, Sequential
from keras.optimizers import Adam# number of possible label values
nb_classes = 7# Initialising the CNNmodel = Sequential()# 1 - Convolutionmodel.add(Conv2D(64,(3,3), padding='same', input_shape=(48, 48,1)))model.add(BatchNormalization())model.add(Activation('relu'))model.add(MaxPooling2D(pool_size=(2, 2)))model.add(Dropout(0.25))# 2nd Convolution layermodel.add(Conv2D(128,(5,5), padding='same'))model.add(BatchNormalization())model.add(Activation('relu'))model.add(MaxPooling2D(pool_size=(2, 2)))model.add(Dropout(0.25))# 3rd Convolution layermodel.add(Conv2D(512,(3,3), padding='same'))model.add(BatchNormalization())model.add(Activation('relu'))model.add(MaxPooling2D(pool_size=(2, 2)))model.add(Dropout(0.25))# 4th Convolution layermodel.add(Conv2D(512,(3,3), padding='same'))model.add(BatchNormalization())model.add(Activation('relu'))model.add(MaxPooling2D(pool_size=(2, 2)))model.add(Dropout(0.25))# Flatteningmodel.add(Flatten())# Fully connected layer 1st layermodel.add(Dense(256))model.add(BatchNormalization())model.add(Activation('relu'))model.add(Dropout(0.25))# Fully connected layer 2nd layermodel.add(Dense(512))model.add(BatchNormalization())model.add(Activation('relu'))model.add(Dropout(0.25))model.add(Dense(nb_classes, activation='softmax'))opt = Adam(lr=0.0001)model.compile(optimizer=opt, loss='categorical_crossentropy', metrics=['accuracy'])

模型评估

模型测试

测试案例 1:

测试案例 2:

3.3 模型整合

我们把每个训练出的模型进行接口的匹配连接。最终呈现出:先将图片输入看图说话的模型分析出图片的 captio,将 caption 存储下来。然后将图片再输入微表情识别的模型,若在模型中检测到人脸,就对人脸的表情进行分析得到情绪,最终将情绪和 caption 进行匹配。最终输出图片和对应的 caption 和 emotion(若检测不到人脸则不输出 emotion)示例如下:

♻️ 资源

大小:9.70MB

➡️ 资源下载:https://download.csdn.net/download/s1t16/87354365

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

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

相关文章

android 表情退格,讯飞输入法Android V8.1.8212 嘘-别说话全套emoji表情上

emoji表情大家都不陌生,一个个“小黄脸”在手机聊天场景中高频出现。随着蹿红网络,emoji表情甚至跑进大电影中成为主角。为何大家如此喜欢它?因此各式表情很像不同心情的自己,开心能用、难过能用、不想说也能用,万能回…

java语言c语言表情包_c语言表情包 - c语言微信表情包 - c语言QQ表情包 - 发表情 fabiaoqing.com...

C语言从研发到脱发_脱发_研发_语言表情 熊猫头写个C语言(666)_666_写个_熊猫_语言表情 有了这些,还要女朋友干嘛!(C语言、C++、微积分、CSS、Visual Studio)_CSS_C++_Studio_Visual_微积分表情 如果你有一个朋友很久没跟你联系了一是他死了是他是学汉语言的三是他在期末考试如…

Python代码画哆啦A梦战斗猫--Turtle画图

效果图 代码如下 import turtleturtle.speed(5) turtle.circle(50) turtle.begin_fill() #画头 turtle.circle(85) turtle.fillcolor("blue") turtle.end_fill()# turtle.penup() # turtle.goto(0,20) # turtle.pendown()# turtle.begin_fill() # turtle.c…

Python turtle 画圣诞树

马上就要圣诞街了,作为一名程序猿的我们应该用代码表达一下程序猿的温柔呐,所以,改写了一段Python画圣诞树的代码,给你们的朋友们画一颗代码圣诞树吧! 圣诞树一 import turtle as t #as就是取个别名,后续调用的t都是…

Python之turtle画小狗、狮子头和小黄人

源码 from turtle import * import turtle as t t.screensize(500, 500) # 【头部轮廓】 t.pensize(5) t.home() t.seth(0) t.pd() t.color(black) t.circle(20, 80) # 0 t.circle(200, 30) # 1 t.circle(30, 60) # 2 t.circle(200, 29.5) # 3 t.color(black) t.circle(20…

python海龟绘图(turtle)手绘【玫瑰、时钟、哆啦A梦、小猪佩奇、史迪仔】

前言 python的第三方库绘图模块turtle(因其本意有海龟、乌龟的意思,又称为海龟绘图),可以用来绘制一些很好玩的东西。之前就有盛极一时的海龟绘图绘制冰墩墩,这里给大家总结了关于海龟绘图的一些方法方便大家学习。另…

用python画樱花、玫瑰和圣诞树

最近翻到一篇知乎,上面有不少用Python(大多是turtle库)绘制的树图,感觉很漂亮,我整理了一下,挑了一些我觉得不错的代码分享给大家(这些我都测试过,确实可以生成喔~) one…

【C语言】如何用C语言画一个哆啦A梦(附源代码)

大雄有一天打开自己的课桌,一只猫型机器人突然从抽屉里跳了出来,而这就是哆啦A梦,它是从未来世界穿越过来帮助大雄的。 今天教大家如何用C语言来画一个哆啦A梦(可爱版) 我这里用的是2019的VS,没有安装的朋友…

Python:通过turtle 画樱花树

文章目录 简介动态生成樱花飘落效果暗色效果小结 简介 文章主要介绍了如何基于python实现画不同品种的樱花树,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 动态生成樱花 实现代码: import turt…

如何制作铃铛

作者:周湘,撰写时间:2019年2月3日 我们进入Ps新建文件,然后把图片准备好,准备好之后开始制作铃铛。 第一部分,先做两个大小不一样的圆,这两个圆的颜色有不同,分别是一个橘黄色和一个…

用python画圣诞树、樱花树、卡通图案及打包成exe文件

文章目录 用python画圣诞树、樱花树、卡通图案及打包成exe文件效果1、圣诞树--朴素2、圣诞树--可爱3、圣诞树--飘雪4、樱花树--飘落效果5、樱花树--暖色调6、哆啦a梦 用python画圣诞树、樱花树、卡通图案及打包成exe文件 如何将python代码生成exe文件,直接在桌面运…

用python画小仓鼠教程_彩色铅笔画步骤教程:小仓鼠的画法

材料和工具准备 铅笔、橡皮 36色彩色铅笔、素描纸 完成图效果 Step线稿起型 1.观察仓鼠的体态特征,头部和身体形成一个整体。 2.用2B铅笔勾出五官和身体的轮廓,确定位置。 提示:随着绘画的深入,我们会逐渐将线条擦掉,在…

用Python画圣诞树 ‘‘遇见’’ 圣诞老人

这是雪程序的1.1版本。 上个版本的文章---看这里: 忙活半天只为了看雪--送给大家的冬至礼物https://blog.csdn.net/qq_54554848/article/details/121873955?spm1001.2014.3001.5501(下述代码基于上个版本) 上次我发布了--冬至礼物的博客&…

用python画机器猫--哆啦A梦,开干!

python 画哆啦A梦 大家好,我是Dream,今天在视频中无意间看到了哆啦A梦,这让我勾起了许多童年回忆,不知道大家有没有看过哆啦A梦呢? 那我们能不能用python画出哆啦A梦来呢?话不多说,让我们行起来…

教程 | 10分钟入门禅绕画 (下)

禅绕装饰画是一种意识流装饰画,也是一种有趣随性的涂鸦,笔触可以天马星空随意走动。 禅绕画的构图技巧:重复、对称、均衡、重叠、勾线、肌理等。所有技巧的组合可以使得画面节奏和谐、疏密有度、节奏韵律恰如其分。 以下为铃铛子绘制的禅绕画…

AE铃铛摆动动画

一、概述 这是一篇非编程向、数学向、物理向的技术探讨小文,一切从视觉效果出发,向设计师朋友们介绍如何通过表达式而不需要手动K帧的方式来实现真实细腻的铃铛摆动动画。 二、制作步骤 1.绘制铃铛 2.整理图层关系 使用AEUX插件将在Sketch中绘制的铃铛…

小兔子怎么画?非常详细的小动物绘画教程!

小兔子怎么画?小动物怎么画?绘画初学者如何学习绘画?萌新小白如何入门插画?学习绘画难吗?怎样才能学好绘画?想必这些都是绘画初学者们经常在想的问题吧,就是不知道如何才能学习好绘画&#xff0…

如何画一只年兽(附代码及教程)

画年兽嘛,其实是一件特别费脑细胞而且特别麻烦的事——特别是在坐标、线段长度等参数还没有确定的情况下。本人冒着头发掉光的危险,画了一个年兽(???)(如下图) 快过年了…

教程 | 10分钟入门禅绕画 (上)

有人说,禅绕画没有任何美术基础也可以画的好的。 事实上,缠绕画对美学的感受能力有一定要求,想画好禅绕画需要熟知多个常用的禅绕元素。只有在掌握基本元素后,勤加练习,才能随心所欲想画就画。 我个人认为,…

用Python画一只溜达小狗——turtle库基础入门

一只脑门有点方的小狗,其实还可以把脑门和后脑勺完善一下,更圆润一些。 但这样也挺可爱,就保有这样不完美但独一无二的它吧。绘制过程主要就是拼接和调整圆弧,尽量做到过度自然。 小狗的绘制主要使用了turtle库的circle()函数&am…