专栏首页相约机器人如何教电脑玩Spot it!使用OpenCV和深度学习

如何教电脑玩Spot it!使用OpenCV和深度学习

来源 | Medium

编辑 | 代码医生团队

爱好是玩棋盘游戏,因为对CNN有所了解,所以决定开发一种可以在纸牌游戏中击败人类的应用程序。想使用我自己的数据集从头开始构建模型,以查看使用小数据集从头开始的模型的性能如何。选择从一个不太难的游戏入手!(又称Dobble)。

如果不知道它!这里有一个简短的游戏说明:是一款简单的模式识别游戏,玩家可以尝试查找两张卡上显示的图像。每张卡都在原厂现货中!具有八个不同的符号,符号的大小从一张卡到另一张卡都不同。任何两张卡共有一个符号。如果是第一个找到该符号的人,那么将赢得该卡。55张纸牌用完时,收集最多纸牌的人将获胜。

自己尝试:上面显示的卡上常见的符号是什么?

从哪儿开始?

任何数据科学问题的第一步都是收集数据。用手机拍了一些照片,每张卡有六个。总共制作了330张照片。其中四个如下所示。可能会想:这足以构建完善的卷积神经网络吗?会回到这一点!

处理图像

好的有数据,下一步是什么?这可能是成功的最重要部分:处理图像。需要提取每张卡上显示的符号。这里有一些困难。可以在上面的图片中看到一些符号可能更难提取:雪人和幽灵(第三张图片)和圆顶冰屋(第四张图片)的颜色浅,并且有污点(第二张图片)和感叹号(第四张图片)多个部分。为了处理浅色符号,向图像添加了对比度。之后调整大小并保存图像。

增加对比

使用Lab颜色空间来增加对比度。L代表亮度,a是从绿色到品红色的颜色分量,b是从蓝色到黄色的颜色分量。可以使用OpenCV轻松提取这些组件:

import cv2
import imutils
imgname = 'picture1'
image = cv2.imread(f’{imgname}.jpg’)
lab = cv2.cvtColor(image, cv2.COLOR_BGR2LAB)
l, a, b = cv2.split(lab)

从左到右:原始图像,Light组件,a组件和b组件

现在向Light组件添加对比度,将这些组件合并在一起,然后将图像转换回正常状态:

clahe = cv2.createCLAHE(clipLimit=3.0, tileGridSize=(8,8))
cl = clahe.apply(l)
limg = cv2.merge((cl,a,b))
final = cv2.cvtColor(limg, cv2.COLOR_LAB2BGR)

从左到右:原始图像,光分量,增加的对比度,转换回RGB

调整大小

然后调整大小并保存图像:

resized = cv2.resize(final, (800, 800))
# save the image
cv2.imwrite(f'{imgname}processed.jpg', blurred)

检测卡和符号

现在处理图像,可以从检测图像上的卡开始。使用OpenCV可以找到外部轮廓。然后需要将图像转换为灰度,选择一个阈值(在本例中为190)以创建黑白图像,然后找到轮廓。在代码中:

image = cv2.imread(f’{imgname}processed.jpg’)
gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
thresh = cv2.threshold(gray, 190, 255, cv2.THRESH_BINARY)[1]
# find contours
cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)
output = image.copy()
# draw contours on image
for c in cnts:
cv2.drawContours(output, [c], -1, (255, 0, 0), 3)

处理后的图像,转换为灰度,阈值并具有外部轮廓

如果按区域对外部轮廓进行排序,则可以找到面积最大的轮廓:这就是卡片。可以创建一个白色背景来提取符号。

# sort by area, grab the biggest one
cnts = sorted(cnts, key=cv2.contourArea, reverse=True)[0]
# create mask with the biggest contour
mask = np.zeros(gray.shape,np.uint8)
mask = cv2.drawContours(mask, [cnts], -1, 255, cv2.FILLED)
# card in foreground
fg_masked = cv2.bitwise_and(image, image, mask=mask)
# white background (use inverted mask)
mask = cv2.bitwise_not(mask)
bk = np.full(image.shape, 255, dtype=np.uint8)
bk_masked = cv2.bitwise_and(bk, bk, mask=mask)
# combine back- and foreground
final = cv2.bitwise_or(fg_masked, bk_masked)

遮罩,背景,前景,组合

现在是符号检测时间!可以使用最后一张图像再次检测外部轮廓,这些轮廓是符号。如果在每个符号周围创建一个正方形,则可以提取该区域。代码更长一些:

# just like before (with detecting the card)
gray = cv2.cvtColor(final, cv2.COLOR_RGB2GRAY)
thresh = cv2.threshold(gray, 195, 255, cv2.THRESH_BINARY)[1]
thresh = cv2.bitwise_not(thresh)
cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)
cnts = sorted(cnts, key=cv2.contourArea, reverse=True)[:10]
# handle each contour
i = 0
for c in cnts:
    if cv2.contourArea(c) > 1000:
        # draw mask, keep contour
        mask = np.zeros(gray.shape, np.uint8)
        mask = cv2.drawContours(mask, [c], -1, 255, cv2.FILLED)
        # white background
        fg_masked = cv2.bitwise_and(image, image, mask=mask)
        mask = cv2.bitwise_not(mask)
        bk = np.full(image.shape, 255, dtype=np.uint8)
        bk_masked = cv2.bitwise_and(bk, bk, mask=mask)
        finalcont = cv2.bitwise_or(fg_masked, bk_masked)
        # bounding rectangle around contour
        output = finalcont.copy()
        x,y,w,h = cv2.boundingRect(c)
        # squares io rectangles
        if w < h:
            x += int((w-h)/2)
            w = h
        else:
            y += int((h-w)/2)
            h = w
        # take out the square with the symbol
        roi = finalcont[y:y+h, x:x+w]
        roi = cv2.resize(roi, (400,400))
        # save the symbol
        cv2.imwrite(f"{imgname}_icon{i}.jpg", roi)
        i += 1

阈值图像,找到的轮廓,重影符号和心脏符号(使用遮罩提取的符号)

排序符号

现在是无聊的部分!现在该对符号进行排序了。需要一个训练,测试和验证目录,每个目录包含57个目录(有57个不同的符号)。文件夹结构如下所示:

symbols

├── test

│ ├── anchor

│ ├── apple

│ │ ...

│ └── zebra

├── train

│ ├── anchor

│ ├── apple

│ │ ...

│ └── zebra

└── validation

├── anchor

├── apple

│ ...

└── zebra

将提取的符号(超过2500个)放在正确的目录中需要花费一些时间!有用于在GitHub上创建子文件夹,测试和验证集的代码。也许下次最好使用聚类算法进行排序…

https://github.com/henniedeharder/spotit/tree/master/DeepLearningSpotIt

训练卷积神经网络

在无聊的部分之后又是酷的部分。构建和训练一个CNN。可以在这篇文章中找到有关CNN的信息。

模型架构

这是一个多类,单标签的分类问题。希望每个符号都有一个标签。这就是为什么必须选择具有57个节点和分类交叉熵损失函数的最后一层激活softmax的原因。

最终模型的架构如下所示:

# imports
from keras import layers
from keras import models
from keras import optimizers
from keras.preprocessing.image import ImageDataGenerator
import matplotlib.pyplot as plt
# layers, activation layer with 57 nodes (one for every symbol)
model = models.Sequential()
model.add(layers.Conv2D(32, (3, 3), activation='relu', input_shape=(400, 400, 3)))
model.add(layers.MaxPooling2D((2, 2)))  
model.add(layers.Conv2D(64, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(128, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(256, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(256, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(128, (3, 3), activation='relu'))
model.add(layers.Flatten())
model.add(layers.Dropout(0.5))
model.add(layers.Dense(512, activation='relu'))
model.add(layers.Dense(57, activation='softmax'))
model.compile(loss='categorical_crossentropy',       optimizer=optimizers.RMSprop(lr=1e-4), metrics=['acc'])

资料扩充

为了获得更好的性能,使用了数据扩充。数据扩充是增加输入数据的数量和多样性的过程。这可以通过旋转,移动,缩放,裁剪和翻转现有图像来实现。使用Keras进行数据扩充很容易:

# specify the directories
train_dir = 'symbols/train'
validation_dir = 'symbols/validation'
test_dir = 'symbols/test'
# data augmentation with ImageDataGenerator from Keras (only train)
train_datagen = ImageDataGenerator(rescale=1./255, rotation_range=40, width_shift_range=0.1, height_shift_range=0.1, shear_range=0.1, zoom_range=0.1, horizontal_flip=True, vertical_flip=True)
test_datagen = ImageDataGenerator(rescale=1./255)
train_generator = train_datagen.flow_from_directory(train_dir, target_size=(400,400), batch_size=20, class_mode='categorical')
validation_generator = test_datagen.flow_from_directory(validation_dir, target_size=(400,400), batch_size=20, class_mode='categorical')

如果想知道,一个增强的幽灵看起来像这样:

左侧为原始鬼影,其他图像为增强鬼影

拟合模型

拟合模型,将其保存以用于预测并检查结果。

history = model.fit_generator(train_generator, steps_per_epoch=100, epochs=100, validation_data=validation_generator, validation_steps=50)
# don't forget to save your model!
model.save('models/model.h5')

完美的预测!

结果

训练的基准模型没有数据扩充,丢失和层次减少的问题。该模型得出以下结果:

基线模型的结果

可以清楚地看到此模型过度拟合。最终模型的结果(来自前面各段中的代码)要好得多。在下图中,可以看到训练和验证集的准确性和损失。

最终模型的结果

使用测试集,该模型仅犯了一个错误:它预测炸弹会掉落。决定坚持使用该模型,测试集的准确性为0.995。

预测两张牌的共同符号

现在可以预测两张卡上的通用符号。可以使用两个图像,分别对每个图像进行预测,并使用交集查看两个卡都有什么符号。这提供了三种可能性:

  • 在预测期间出了点问题:找不到常见的符号。
  • 相交处只有一个符号(可以是错误的或正确的)。
  • 相交处有多个符号。在这种情况下,选择了概率最高的符号(两个预测的均值)。

该代码位于GitHub上,用于预测目录main.py文件中两个图像的所有组合。

https://github.com/henniedeharder/spotit/tree/master/DeepLearningSpotIt

一些结果:

结论

这是一个完美的表现模型吗?抱歉不行!当为卡片拍摄新照片并让模型预测通用符号时,雪人遇到了一些问题。有时它预示着眼睛或斑马像个雪人!这给出了一些奇怪的结果:

雪人?哪里?

这个模型比人类好吗?这取决于:人类可以做到完美,但是模型更快!给计算机计算时:给它提供了55张卡,并询问两个卡的每种组合的通用符号。总共有1485个组合。这花费了计算机不到140秒的时间。电脑犯了一些错误,但绝对可以打败任何人!

认为建立100%绩效的模型并不难。例如,可以通过使用迁移学习来完成。为了了解模型在做什么,可以可视化测试图像的层。下次可以尝试的事情!

本文分享自微信公众号 - 相约机器人(xiangyuejiqiren),作者:代码医生

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-04-24

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 使用OpenCV和Python构建运动热图视频

    OpenCV(或称为“ 开源计算机视觉”)是英特尔于1999年开发的一个库,主要针对计算机视觉和实时视频操作,它使用C ++编写,但受不同语言(包括Python...

    代码医生工作室
  • 具有TensorFlow,Keras和OpenCV的实时口罩检测器

    在本文中,将使用Prajna Bhandary创建的口罩数据集。此数据集由属于1376个的图像with mask和without mask2类。

    代码医生工作室
  • 在Python中使用OpenCV绘画和素描

    OpenCV是功能强大的计算机视觉库,具有强大的图像处理工具包。在本文中将利用它来创建绘图和绘画,其中大多数将使用内置功能!简短介绍一下,直接进入令人兴奋的部分...

    代码医生工作室
  • OpenCV:人脸检测。

    小F
  • [python opencv 计算机视觉零基础到实战] 八、ROI泛洪填充

    ROI指的是region of Interest,翻译过来就是你所感兴趣的区域。弱在一张图片中,你感兴趣的是某一个区域,那么这个区域就可以称为ROI。我们通过一...

    公众号 碧油鸡
  • OpenCV学习笔记(Python)

    警告: 就算图像的路径是错的, OpenCV 也不会提醒你的,但是当你使用命 令print img时得到的结果是None。

    一点儿也不潇洒
  • [图像处理] Python+OpenCV实现车牌区域识别

    这里原理推荐我以前C++图像处理的文章,如下:https://blog.csdn.net/column/details/eastmount-mfc.html

    统计学家
  • OpenCV从零基础---检测及分割图像的目标区域

    作者:王抒伟 编辑:王抒伟 算了 爱看多久看多久 零 参考目录: 1.获取图片 2.转换灰度并去噪声 3.提取图像的梯度 4.我们继续去噪声 5.图像形态学...

    机器学习算法工程师
  • Halcon缺陷检测实例转OpenCV实现(三)

    本期文章继续介绍缺陷检测专题的第三个案例,用OpenCV实现Halcon中一个物体凸缺陷检测的实例,前两个案例链接如上↑↑↑。

    Color Space
  • opencv 2 -- 形态学处理

    一、 图像腐蚀 图像腐蚀: 卷积核沿着图像滑动,如果与卷积核对应的原图 像的所有像素值都是 1,那么中心元素就保持原来的像素值,否则就变为零。

    wust小吴

扫码关注云+社区

领取腾讯云代金券