作者:张晟

来源:人工智能与设计

某日工作室学妹问我,看视频学人工智能好枯燥,有没有实际项目可以实践下?

正巧室友刚做了一个识别剪刀石头布的图像识别程序,于是脑洞大开,改造了一下,做了这个识别结印手势来发动忍术的小游戏。

演示视频:

这里我就把项目整理成教程,让大家都能做脑洞大开的创作。

感谢室友陆玄青提供的简单图像识别源码https://github.com/LuXuanqing/tutorial-image-recognition

改造后的识别手势玩火影忍者忍术源码在这里:

https://github.com/Arthurzhangsheng/NARUTO_game

本项目不要求有人工智能基础,但要有python基础,需要的环境:

  • tensorflow1.1

  • keras

  • opencv

  • python3

  • ffmpeg

  • PIL

  • pathlib

  • shutil

  • imageio

  • numpy

  • pygame

  • 一个摄像头

整体流程

下载源码后,用jupyternotebook打开tutorial.ipynb文件,按照里面的教程,一步一步运行,全部运行过后,就得到训练好的能识别手势的神经网络模型文件。运行model文件夹下的predict.py,即可开始试玩。

注意事项

我用的vscode编辑器,把当前工作路径设置为 NARUTO_game 这个主文件夹,并以此设置相关的相对路径,若直接cd到model文件夹来运行predict.py文件,需要手动调整源码中的相对路径。

其实教程具体操作已经全部写在tutorial.ipynb里了,为让大家更直观了解整个操作过程,这里就把tutorial.ipynb里的文字复制搬运到这里来。

Step1 - 采集数据

用手机拍摄视频记录你想要识别的物体。每段视频中只能包含一种物体,时长10~30秒,每个物体可以拍摄多段视频。视频尽量用4:3或1:1的长宽比,分辨率低越好(注意是低)。

进入data/video文件夹,为每种物体(手势)新建一个文件夹,然后把相应的视频导入进去。例如我拍摄了5段关于猫的视频和3段关于狗的视频,就在data/video文件夹下新建dog、cat两个文件夹,然后把把猫的视频全部放进cat文件夹,把狗的视频全部放进dog文件夹,视频的文件名无所谓。

识别结印手势的话我分了14类,12个结印为一类,空白动作为1类,还增加了一个取消动作(虽然这次并没有用到),一共14类。视频文件太大我就没传到github源码上了,大家可以自己用电脑摄像头录一下。

Step2 - 数据处理

在这一步,我们需要把视频转成图片,然后按照60%、20%、20%的比例拆分成训练集(training set)、验证集(validation set)、和测试集(test set)。 为了节省大家时间,我事先已经写好了相关的代码(utils.py),大家只要按照提示进行调用即可完成这一步骤。

  1. import utils#################### 以下是你可以修改的部分 ####################fps = 5 # fps是视频的采样率,即每秒中采集多少张图片,建议设置为5~10# 每张图片的大小,根据你原始视频的比例进行缩放,建议不要超过300x300# 训练所需时间会和图像的像素数量成正比,建议设置得小一点,如160x120width = 160height = 90#################### 以上是你可以修改的部分 ####################utils.process_videos(fps, target_size=(width, height))


Step3 - 数据增强

把一张原始图片经过拉伸、旋转、斜切、反转等操作,可以生产若干新的不同的图片,用以扩充训练集数据量,有助于提高模型的预测准确性。

  1. from keras.preprocessing.image import ImageDataGenerator

  2. from pathlib import Path

  3. # 设置train,val,test的目录

  4. base_dir = Path('data')

  5. train_dir = base_dir/'train'

  6. val_dir = base_dir/'val'

  7. test_dir = base_dir/'test'

  8. # 创建train和val图像生成器,它们会不断地产生出新的图片

  9. #################### 以下是你可以修改的部分 ####################

  10. train_datagen = ImageDataGenerator(rescale=1./255,

  11.                                   rotation_range=10,

  12.                                   width_shift_range=0.2,

  13.                                   height_shift_range=0.2,

  14.                                   shear_range=0.2,

  15.                                   zoom_range=0.2,

  16.                                   horizontal_flip=False,

  17.                                   vertical_flip=True,

  18.                                   fill_mode='nearest')

  19. #################### 以上是你可以修改的部分 ####################

  20. train_generator = train_datagen.flow_from_directory(train_dir, target_size=(height,width))

  21. val_generator = train_datagen.flow_from_directory(val_dir, target_size=(height,width))

  22. # test的时候是模拟真实环境,所以要使用原始图片,不要对图片进行任何操作

  23. test_datagen = ImageDataGenerator(rescale=1./255)

  24. test_generator = test_datagen.flow_from_directory(test_dir, target_size=(height,width))


Step4 - 搭建卷积神经网络

在这一步我们要搭建神经网络的架构。 图像识别的常见方法是通过卷积操作提取图片中的特征,然后将特征输入到神经网络中,最后神经网络输出结果。所以在这一阶段,我们要分别准备卷积和神经网络两个部分。

4.1 - 卷积部分


迁移学习(transfer learning)

对图像进行卷积操作需要耗费大量计算资源,并且训练需要巨大的数据量,一般个人是搞不定这事的。 好消息是人们发现了一个有趣的现象:训练出来用于识别A物体的卷积神经网络,它的卷积部分也能够很好地被用于识别B物体。 所以我们可以把人家已经训练好的NB的卷积神经网络借来用,这就是迁移学习。


载入VGG16

VGG16是一个非常经典的卷积神经网络,16代表有16个层,前13层是卷积层,后3层是全连阶层。我们需要使用它的前13个卷积层,并且使用这些层的权值,用来从图像中提取特征。然后把提取后的特征输入到我们自己的神经网络中进行识别。

  1. import keras as K

  2. # load pretrained model and weights

  3. conv_layers = K.applications.VGG16(include_top=False, input_shape=(height,width,3))

  4. conv_layers.trainable = False

  5. print('per-trained model has been loaded')


4.2 - 神经网络部分


  1. model = K.models.Sequential()

  2. model.add(conv_layers)#载入VGG16的卷积部分

  3. model.add(K.layers.Flatten())#拉平成一维

  4. n_classes = len(utils.get_child_dir_names(base_dir/'video'))

  5. # 以下是你可以修改的部分

  6. model.add(K.layers.Dense(2048, activation='relu'))

  7. model.add(K.layers.Dropout(0.5))

  8. model.add(K.layers.Dense(2048, activation='relu'))

  9. model.add(K.layers.Dropout(0.5))

  10. # 以上是你可以修改的部分

  11. model.add(K.layers.Dense(n_classes, activation='softmax'))

  12. print('以下是神经网络的架构:')

  13. model.summary()

Step5 - 训练及验证

可以尝试选择不同的优化器和优化器参数(Keras文档),好的优化器能让训练结果尽快收敛并获得更高的准确率。

  1. model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['acc'])

  2. print('优化器设置完毕')

下面开始训练,为了节省时间只设置了迭代20次。你可以尝试不同迭代次看看它数对最终结果的影响。

  1. n_epochs = 20

  2. n_train_samples = utils.count_jpgs(train_dir)#训练集图片总数

  3. n_val_samples = utils.count_jpgs(val_dir)#val集图片总数

  4. batch_size = 32

  5. history = model.fit_generator(train_generator, steps_per_epoch=n_train_samples/batch_size, epochs=n_epochs,

  6.                              validation_data=val_generator, validation_steps=n_val_samples/batch_size, verbose=2)

  7. print('训练完毕')

画图看一下训练效果

  1. from matplotlib import pyplot as plt

  2. fig = plt.figure(figsize=(8,4), dpi=100)

  3. plt.plot(range(n_epochs), history.history['acc'], 'c', label='Training Accuracy', aa=True)

  4. plt.plot(range(n_epochs), history.history['val_acc'], 'darkorange', label='Validation Accuracy', aa=True)

  5. plt.legend()

  6. plt.xlabel('epoch')

  7. plt.ylabel('Accuracy')

  8. plt.ylim(0,1)

  9. plt.grid()

  10. plt.show()

怎么看训练的结果好不好 好的情况 总体上来看,train和val的正确率都随着迭代次数增加而上升,并且最后收敛于某一个比较高的数值。 两种不好的情况:

1.欠拟合(under-fitting)

train和val的正确率都比较低。 造成这种情况的原因有很多,常见的有:数据量不够大、神经网络设计得不合理、优化器选择不合理、迭代次数不够。

2.过拟合(over-fitting)

train的正确率很高,但是val正确率很低。 这种情况代表模型的泛化能力不好,它完全适应了训练集的数据(可以接近100%的正确率),但是不适用于验证集的数据。 解决方法是使用在Dense层后追加Dropout层或是在Densse层的选项中设置regularizer。

Step6 - 测试

如果上面的验证结果还不错,那恭喜你就快要成功了! 最后我们用测试集的数据来测试一下。

  1. n_test_samples = utils.count_jpgs(test_dir)

  2. _, test_acc = model.evaluate_generator(test_generator, steps=n_test_samples/batch_size)

  3. print('测试正确率:{}'.format(test_acc))


Step7 - 拍张照,让程序来判断它是什么

拍一张照,上传到 data/x 文件夹中,默认文件名是 myimage.jpg。如果你保存了其它文件名或是其它文件夹,需要修改下方代码中的路径。

先显示一下图片看看对不对

  1. from PIL import Image

  2. path = 'data/test/辰/chen_0.jpg'

  3. img = Image.open(path)

  4. img.show()

让程序来预测试试吧

  1. x = utils.preprocess(img, (width, height))

  2. y = model.predict(x)[0]

  3. class_indices = train_generator.class_indices#获得文件夹名的和类的序号对应的字典

  4. class_indices_reverse={v:k for k,v in class_indices.items()}#反转字典的索引和内容值

  5. utils.show_pred(y,class_indices_reverse)


Optional - 用自己电脑的摄像头做实时预测

先保存训练好的模型文件

  1. model.save('model/NARUTO.h5')

  2. utils.save_confg(class_indices_reverse,input_size=(160,90),fp='model/config.json')

  3. print('保存成功')

然后运行其中model文件夹下的的predict.py即可。

这里有个注意事项:我用的vscode编辑器,把当前工作路径设置为 NARUTO_game 这个主文件夹,并以此设置相关的相对路径,若直接cd到model文件夹来运行predict.py文件,需要手动调整源码中的相对路径

然后predict.py文件里大部分是关于如何根据识别到的图像结果,来做出放音效,放gif特效等操作,就不展开细讲每一步在做什么了,大家可以自己发挥想象力去改造。

用到的一些音效、gif图、字体也都放在源码仓库里了。

  1. # coding=utf-8

  2. from keras import models

  3. import numpy as np

  4. import cv2

  5. import json

  6. import os

  7. from PIL import Image, ImageDraw, ImageFont  

  8. import pygame,time

  9. def load_config(fp):

  10.    with open(fp,encoding='UTF-8') as f:

  11.        config = json.load(f, encoding='UTF-8')

  12.        indices = config['indices']

  13.        input_size = config['input_size']

  14.        return indices, input_size

  15. def decode(preds, indices):

  16.    results = []

  17.    for pred in preds:

  18.        index = pred.argmax()

  19.        result = indices[str(index)]

  20.        results.append(result)

  21.        result = results[0]

  22.    return result

  23. def preprocess(arr, input_size):

  24.    input_size = tuple(input_size)

  25.    # resize

  26.    x = cv2.resize(arr, input_size)

  27.    # BGR 2 RGB

  28.    x = cv2.cvtColor(x, cv2.COLOR_BGR2RGB)

  29.    x = np.expand_dims(x, 0).astype('float32')

  30.    x /= 255

  31.    return x

  32. def put_text_on_img(img,

  33.                    text='文字信息',

  34.                    font_size = 50,

  35.                    start_location = (100, 0),

  36.                    font_color = (255, 255, 255),

  37.                    fontfile = 'model/font.ttf'):

  38.    '''

  39.    读取opencv的图片,并把中文字放到图片上

  40.    font_size = 100             #字体大小

  41.    start_location = (0, 0)     #字体起始位置

  42.    font_color = (0, 0, 0)      #字体颜色

  43.    fontfile = 'model/font.ttf' #字体文件

  44.    '''

  45.    # cv2读取图片  

  46.    cv2img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # cv2和PIL中颜色的hex码的储存顺序不同  

  47.    pilimg = Image.fromarray(cv2img)  

  48.    # PIL图片上打印汉字  

  49.    draw = ImageDraw.Draw(pilimg) # 图片上打印  

  50.    font = ImageFont.truetype(fontfile, font_size, encoding="utf-8") # 参数1:字体文件路径,参数2:字体大小  

  51.    draw.text(start_location, text, font_color, font=font) # 参数1:打印坐标,参数2:文本,参数3:字体颜色,参数4:字体  

  52.    # PIL图片转cv2 图片  

  53.    convert_img = cv2.cvtColor(np.array(pilimg), cv2.COLOR_RGB2BGR)  

  54.    # cv2.imshow("图片", cv2charimg) # 汉字窗口标题显示乱码  

  55.    return convert_img

  56. def playBGM():

  57.    bgm_path = r'audio/BGM.mp3'

  58.    pygame.mixer.init()

  59.    pygame.mixer.music.load(bgm_path)

  60.    pygame.mixer.music.set_volume(0.2)

  61.    pygame.mixer.music.play(loops=-1)

  62. def playsound(action):

  63.    sound_path1 = 'audio/test1.wav'

  64.    sound_path2 = 'audio/test2.wav'

  65.    sound_path3 = 'audio/huituzhuansheng.wav'

  66.    sound_path4 = 'audio/yingfenshen.wav'

  67.    if action == "寅":

  68.        sound1 = pygame.mixer.Sound(sound_path2)

  69.        sound1.set_volume(0.3)

  70.        sound1.play()

  71.    elif action == "申":

  72.        sound1 = pygame.mixer.Sound(sound_path1)

  73.        sound1.set_volume(0.5)

  74.        sound1.play()

  75.    elif action == '酉':

  76.        sound1 = pygame.mixer.Sound(sound_path3)

  77.        sound1.set_volume(1)

  78.        sound1.play()        

  79.    elif action == "丑":

  80.        sound1 = pygame.mixer.Sound(sound_path4)

  81.        sound1.set_volume(1)

  82.        sound1.play()  

  83.    else:

  84.        pass    

  85. def add_gif2cap(cap, pngimg):

  86.    # I want to put logo on top-left corner, So I create a ROI

  87.    rows1,cols1,channels1 = cap.shape

  88.    rows,cols,channels = pngimg.shape

  89.    roi = cap[(rows1-rows)//2:(rows1-rows)//2+rows, (cols1-cols)//2:(cols1-cols)//2+cols ]

  90.    # Now create a mask of logo and create its inverse mask also

  91.    img2gray = cv2.cvtColor(pngimg,cv2.COLOR_BGR2GRAY)

  92.    ret, mask = cv2.threshold(img2gray, 180, 255, cv2.THRESH_BINARY)

  93.    mask_inv = cv2.bitwise_not(mask)

  94.    # Now black-out the area of logo in ROI

  95.    img1_bg = cv2.bitwise_and(roi,roi,mask = mask_inv)

  96.    # Take only region of logo from logo image.

  97.    img2_fg = cv2.bitwise_and(pngimg,pngimg,mask = mask)

  98.    # Put logo in ROI and modify the main image

  99.    dst = cv2.add(img1_bg,img2_fg)

  100.    cap[(rows1-rows)//2:(rows1-rows)//2+rows, (cols1-cols)//2:(cols1-cols)//2+cols] = dst

  101.    return cap

  102. def add_gif2cap_with_action(action, cap, png_num):

  103.    if action == "寅":

  104.        pngpath = 'image/shuilongdan/png/action-%02d.png'%(png_num)

  105.        pngimg = cv2.imread(pngpath)

  106.        pngimg = cv2.resize(pngimg,None,fx=0.8, fy=0.8, interpolation = cv2.INTER_CUBIC)

  107.        cap = add_gif2cap(cap, pngimg)

  108.        return cap

  109.    else:

  110.        return cap

  111. def main():

  112.    indices, input_size = load_config('model/config.json')

  113.    model = models.load_model('model/NARUTO.h5')

  114.    cap = cv2.VideoCapture(0)

  115.    counter = 0      

  116.    counter_temp = 0 #计数器

  117.    action = "子"

  118.    playBGM()

  119.    png_num = 1 #用于计数动画图片序号的变量

  120.    while True:

  121.        _, frame_img = cap.read()

  122.        # predict

  123.        x = preprocess(frame_img,input_size)

  124.        y = model.predict(x)

  125.        action = decode(y,indices)

  126.        #播放音效,且每次播放间隔50个帧

  127.        counter+=1

  128.        if counter == 2:

  129.            #触发音效

  130.            playsound(action)            

  131.            counter += 1

  132.        if counter == 50:

  133.            counter = 0

  134.        #显示动作名  

  135.        frame_img = put_text_on_img(

  136.            img= frame_img,

  137.            text= "當前動作:"+action,

  138.            font_size = 50,

  139.            start_location = (0, 100),

  140.            font_color = (255, 150, 0)

  141.        )

  142.        #触发动画

  143.        if action == "寅":

  144.            frame_img = add_gif2cap_with_action(action, frame_img, png_num)

  145.            png_num += 1

  146.            if png_num >=37:#水龙弹动画有37帧

  147.                png_num=0

  148.        #show image

  149.        cv2.imshow('webcam', frame_img)

  150.        #按Q关闭窗口

  151.        if cv2.waitKey(1) & 0xFF == ord('q'):

  152.            break

  153.    cv2.destroyAllWindows()

  154.    cap.release()

  155. if __name__ == '__main__':

  156.    main()

  157.   # playBGM()

写在最后

我自己代码水平不高,可能引起知乎读者不适,因为编程和AI只是上学期才开始自学的 ಠᴗಠ。

真正的专业是工业设计(〃´-ω・),跟知乎人工智能大神没法比,正在努力学习python和AI中。

- END -

 近期热文:

  • 来自95后的天池中间件大赛总结

  • 为Spring Cloud Config插上管理的翅膀

  • 福利继续:赠书《Spring Cloud微服务-全栈技术与案例解析》

  • Spring Cloud Config采用Git存储时两种常用的配置策略

  • Hystrix降级逻辑中如何获取触发的异常?

  • 重磅剧透!阿里巴巴计划开源 Nacos,为Dubbo生态发展铺路

  • Spring Cloud Config采用数据库存储配置内容

  • 你可能会忽略的 Git 提交规范

  • SpringBoot应用部署于外置Tomcat容器

……

可关注我的公众号

深入交流、更多福利

扫码加入我的知识星球

点击“阅读原文”,看本号其他精彩内容

设计师的AI自学之路:用图像识别玩忍术相关推荐

  1. 《探寻AI创新之路——游戏科技与人工智能创新发展报告》正式发布

    近日,中国科学院虚拟经济与数据科学研究中心.中国科学院大数据挖掘与知识管理重点实验室在京联合举办<探寻AI创新之路--游戏科技与人工智能创新发展报告>发布暨研讨会. 报告下载地址:http ...

  2. Axure RP9 自学之路2-基础操作篇

    关注头条@路飞写代码,获取更多内容 上期回顾 前一篇文章我们主要是介绍了软件的安装,以及学习该软件的一些初衷,以及对软件的一些区域功能进行了相应的说明. 主要知识点 添加元件.设置元件名称.位置尺寸. ...

  3. python自学网站有哪些-Python自学之路-前期准备

    继上一篇「Python自学之路-序」之后,决定开始零基础学习Python了,今天花了点时间去系统的了解下Python,同时也找了一些相关的教程,这里分享给大家. (一)Python可以做什么 1.各式 ...

  4. AI 学习之路——轻松初探 Python 篇(三)

    喜欢小之的文章的可以关注公众号「WeaponZhi」持续关注动态 这是「AI 学习之路」的第 3 篇,「Python 学习」的第 3 篇 Python 字符串使用和 C 语言比较类似,但还有一些我们值 ...

  5. AI 学习之路——轻松初探 Python 篇(一)

    喜欢小之的文章的可以关注公众号「WeaponZhi」持续关注动态 这是「AI 学习之路」的第 1 篇,「Python 学习」的第 1 篇 前言 1. Python 篇的组织结构 不管是学习人工智能还是 ...

  6. 自梦php,PHP菜狗自学之路 云之梦php php之窗 php脚本之

    第一讲 开启PHP学习之路 2016/5/2 20:29 PHP可以做什么? www网站 管理系统: websever wap网站 第二讲 PHP的数据类型 源码调试 2016/5/2 20:30 P ...

  7. Axure RP9 自学之路1-软件初识

    关注头条@路飞写代码,获取更多资料 学习初衷 本人前端工程师一枚,一直以来的开发,都是按照产品经理给出的原型界面来进行Web页面开发,虽然很早就知道有这么一个快速制作原型的工具,但是自己都是没有深入了 ...

  8. 马士兵_JAVA自学之路(为那些目标模糊的码农们)

    JAVA自学之路 一:学会选择  为了就业,不少同学参加各种各样的培训.  决心做软件的,大多数人选的是java,或是.net,也有一些选择了手机.嵌入式.游戏.3G.测试等. 那么究竟应该选择什么方 ...

  9. python自学-分享一位小伙伴的python自学之路,走了哪些弯路?

    原标题:分享一位小伙伴的python自学之路,走了哪些弯路? 你是如何自学Python的? 今天我们来分享一位小伙伴的自学之路.当然,如果你没有任何编程基础,也将会和他一样走很多弯路,如果有条件希望你 ...

最新文章

  1. SpringBoot入门(二)——起步依赖
  2. 文末福利|云原生下Java的变化与趋势?程序员为什么不喜欢低代码?答案在这里!...
  3. 在学习django的时候
  4. (13)python 字典 2 分钟速解
  5. jquery实现单击div切换背景,再次单击回到原来样式
  6. java mysql_Java与mysql的连接
  7. LeetCode - Medium - 264. Ugly Number II
  8. 无惧海量并发,运维准点下班全靠它
  9. 95-138-010-源码-Function-CoFlatMapFunction
  10. linux qt usb转串口通信,centos7 Qt USB转串口通信
  11. 【Shell系列】之shell脚本中常用句法
  12. 视频剪辑用i7,8600还是r5,3600好些?
  13. 软件测试 | 测试计划包含什么内容
  14. 用ip地址远程登录linux的软件,如何远程登陆已知 IP地址的电脑?
  15. Arduino基础项目五:制作彩色LED灯
  16. lol1.7更新服务器维护,lol12月20日维护公告 v3.0.7.1版本更新内容一览
  17. 魅族云同步的实践-协议和架构
  18. C语言中的自定义函数
  19. 输入字符,如果是大写则转化为小写,如果是小写则转化为大写
  20. HTTP中POST和PUT的区别

热门文章

  1. jacoco不是奶茶,却像奶茶一样美味,不尝一下吗?
  2. 优秀课程案例|如何用scratch画折线统计图
  3. 面试之满帮与达达京东到家(未完全解析)
  4. 表格拆分的两种方式 拆分成多个excel工作表或多个excel文件
  5. CentOS安装PostgreSQL各版本
  6. 自建WIFI热点传输应用评测: 还在用蓝牙传文件?你OUT了
  7. order by 按照指定顺序排序或自定义顺序排序
  8. html5带拖拽上传的图片gallary
  9. cocos2d-x屏幕适配原理
  10. [微信] 微信商户号 资金解决方案 自动提现 关闭