人工智能图像实战课:夏天照片归类器

·  阅读 2211
人工智能图像实战课:夏天照片归类器

我正在参加「初夏创意投稿大赛」详情请看:初夏创意投稿大赛

前言

我喜欢用简单的技术,去解决生活中的实际问题,以此来达到技术推广和普及的目的:你看,就是这个原理,挺简单吧!你也可以做一做,我们一起学习,共同成长!

本文,我将以手机中带有夏天元素照片的自动归类为例,来普及人工智能在图片分类中的应用和实践。

一、发现问题

一个夏天,你会拍多少张照片?

你有没有想过,去整理一下相册,去总结一下,这个夏天你都关注过什么?

西瓜、粽子、海滩、烧烤、蝉,还有女票……

夏天照片多.png

我们来详细看看:

唉呀,太乱了,懒得搞,要是有人能帮我整理就好了!算了,他自己都不愿整理,还能帮我?

哎,计算机能自己整理吗?!

你得敢想一些,这个问题,计算机真的会自己整理,这就是人工智能当中的图片分类,而且已经相当成熟了。

二、分析问题

你知道吗?人工智能在修炼成智能之前,都是“智障”。

它像一个刚出生的孩子一样,什么都需要去教:这个是西瓜,那个是风扇……

和孩子不同,人工智能成长的周期比较短,如果数据足够多,他可能分分钟超越你。

下面是一张肺部光学影像图,一般的医生看,他可能说有毛病,因为病理上看是阳性,找几个老医生戴上老花镜在聚光灯前研究五分钟,老医生拍着年轻人的肩膀:仔细看,这是个假阳性!年轻医生仔细一看,哦,确实是!

胸片图片.png

这种假阳性,人工智能1秒钟能看600张,而且还张张准确。

当然,这是有前提的,前提就是你需要像教孩子一样,花费大量精力去训练它,改正它,验证它,最终它才能成为比我们还要快速、准确的人工智能。

我们这个分类夏天照片的问题也一样,我们首先要告诉计算机,什么样的照片属于什么类别,然后训练它,最后它就可以自己进行分类了。

三、解决问题

经过上面的分析,我们明确了要做三件事:

  1. 给现有的夏天照片分类
  2. 将标记好的照片样本交给计算机训练
  3. 让计算机自己分类夏天的照片

3.1 整理训练样本

我大体翻阅了一下照片,发现基本上只有10类,分别是:空调、烧烤、海滩、蝉、赛龙舟、电风扇、雪糕、泳衣、西瓜、粽子。

这么多分类,你要怎样去将他们整理出来,对照片一张张地重命名吗?

不!我们用技术手段,去写一个快速标记器。

图片标注.gif

上图所示,其实它有好多功能:

  • 加载文件夹下的所有图片,然后一张张展示出来
  • 每张图片上展示分类,可以鼠标点击,也可以键盘输入序号,进行标记
  • 标记完成后,将此图片文件名添加前缀,然后自动切换下一张

代码如下所示(python代码):

import tkinter as tk
from tkinter.messagebox import showinfo
import os
from PIL import Image, ImageTk

dir_path = "dataset" # 文件夹路径
prefix = "marked" # 文本标记过后的前缀,防止重复标记

top = tk.Tk()
top.title("图片标记器") 
width = 640
height = 480
top.geometry(f'{width}x{height}') 

# 标记当前图片,并切换到下一个图片
def next_img(type):

    global index
    o_name = img_file_names[index]
    o_path = dir_path+"/"+o_name
    n_name = prefix+"_"+type+"_"+o_name
    n_path = dir_path+"/"+n_name
    # 重命名文件  abc.png-->marked_1_abc.png
    os.rename(o_path, n_path)

    # 如果是最后一个,给出提示,关闭程序
    if index+1 >= len(imgs):
      showinfo(title = '提示',message='已经是最后一个')
      top.destroy() 
      return
    # 索引加1,更换图片
    index = index + 1
    label_img.configure(image=imgs[index])  

imgs = [] # 所有图片
img_file_names = [] # 所有图片的名称
all_files=os.listdir(dir_path)
for f_name in all_files: # 遍历所有图片
    if f_name.startswith(prefix) or not f_name.endswith(".jpg"):
      continue  # 如果已经标注过了,下一个
    img_path = dir_path+"/"+f_name
    img = Image.open(img_path)  # 打开图片
    photo = ImageTk.PhotoImage(img)  # 用PIL模块的PhotoImage打开
    # 把图片和名称存储起来
    imgs.append(photo)
    img_file_names.append(f_name)

# 先展示第一个
index = 0
label_img = tk.Label(top, image=imgs[index])  
label_img.place(x=20, y=20)

class_names = ['空调', '烧烤', '海滩', '蝉', '赛龙舟', '电风扇', '雪糕', '泳衣', '西瓜', '粽子']
    
# 按键的回调
def callback(event):
  c = event.char # 获取键盘输入
  if c in ["0","1","2","3","4","5","6","7","8","9","x"]:
    # 如果输入的是上面的数字,就给这幅图分类改名字
    next_img(str(c))

# 绑定键盘事件
frame = tk.Frame(top, width = 20, height = 20)
frame.bind("<Key>", callback)
frame.focus_set()
frame.pack()

# 摆放数字
b_x = 160 # 基础X位置
b_y = -10 # 基础Y位置
d_l = 75 # 间距
button = tk.Button(top, text="1 "+class_names[1], command=lambda: next_img("1"))
button.place(x=b_x, y=b_y+1*d_l)
button = tk.Button(top, text="2 "+class_names[2], command=lambda: next_img("2"))
button.place(x=b_x, y=b_y+2*d_l)
button = tk.Button(top, text="3 "+class_names[3], command=lambda: next_img("3"))
button.place(x=b_x, y=b_y+3*d_l)
button = tk.Button(top, text="4 "+class_names[4], command=lambda: next_img("4"))
button.place(x=b_x, y=b_y+4*d_l)
button = tk.Button(top, text="5 "+class_names[5], command=lambda: next_img("5"))
button.place(x=b_x, y=b_y+5*d_l)

button = tk.Button(top, text="6 "+class_names[6], command=lambda: next_img("6"))
button.place(x=b_x+d_l, y=b_y+1*d_l)
button = tk.Button(top, text="7 "+class_names[7], command=lambda: next_img("7"))
button.place(x=b_x+d_l, y=b_y+2*d_l)
button = tk.Button(top, text="8 "+class_names[8], command=lambda: next_img("8"))
button.place(x=b_x+d_l, y=b_y+3*d_l)
button = tk.Button(top, text="9 "+class_names[9], command=lambda: next_img("9"))
button.place(x=b_x+d_l, y=b_y+4*d_l)
button = tk.Button(top, text="0 "+class_names[0], command=lambda: next_img("0"))
button.place(x=b_x+d_l, y=b_y+5*d_l)

top.mainloop()
复制代码

通过这个工具,基本上就做到了浏览一遍就标注完成了,标注后的效果如下图所示,同类的在一起。

标记完成后.png

3.2 训练数据

人工智能框架,我使用TensorFlow 2.3版本。

选择TensorFlow,因为我是它的婴幼儿推广大使。选择2.3版本,因为它可以直接从分类好的文件夹读取样本,自动分类,相比之前方便太多了。

我们将上面标注好的图片放到对应的文件夹里,都在dataset下,像下面这样:

整理成文件夹.png

然后就可以写代码了。

首先,导入相关的包:

import tensorflow as tf
import numpy as np
from tensorflow.keras import layers
from tensorflow.keras.models import Sequential
import pathlib
import cv2
import os
from matplotlib import pyplot as plt
import shutil
复制代码

然后,准备要训练的数据集:

# 统计文件夹下的所有图片数量
data_dir = pathlib.Path('dataset')
batch_size = 64
img_width = 256 # 图片宽度
img_height = 256 # 图片高度

# 从文件夹下读取图片,生成数据集
train_ds = tf.keras.preprocessing.image_dataset_from_directory(
    data_dir,
    validation_split=0.2,
    subset='training',
    seed=123,
    image_size=(img_height, img_width),
    batch_size=batch_size
)

val_ds = tf.keras.preprocessing.image_dataset_from_directory(
    data_dir,
    validation_split=0.2,
    subset="validation",
    seed=123,
    image_size=(img_height, img_width),
    batch_size=batch_size
)


# 数据集缓存处理
AUTOTUNE = tf.data.experimental.AUTOTUNE
train_ds = train_ds.cache().shuffle(1000).prefetch(buffer_size=AUTOTUNE)
val_ds = val_ds.cache().prefetch(buffer_size=AUTOTUNE)
复制代码

然后,构建模型:

def create_model(img_height=256, img_width=256, num_classes=10):
    # 构建序列中的层,输入是图片,输出是num_classes,也就是10分类
    model = Sequential([
        layers.experimental.preprocessing.Rescaling(1./255, input_shape=(img_height, img_width, 3)),
        layers.Conv2D(16, 2, padding='same', activation='relu'),
        layers.MaxPooling2D(),
        layers.Dropout(0.2),
        layers.Conv2D(32, 2, padding='same', activation='relu'),
        layers.MaxPooling2D(),
        layers.Conv2D(64, 2, padding='same', activation='relu'),
        layers.MaxPooling2D(),
        layers.Dropout(0.2),
        layers.Flatten(),
        layers.Dense(128, activation='relu'),
        layers.Dense(num_classes)]
    )
    # 配置优化器和损失函数
    model.compile(optimizer='adam',
        loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
        metrics=['accuracy'])

    return model
复制代码

看上面的模型,参数中有图片的宽度和高度,我们默认都是256像素。也就是说,不管你的照片是什么尺寸,都将会被转化为256*256像素进行处理。

下面,就可以训练数据了:

 # 创建模型
model = create_model(img_height,img_width,10)
# 训练结果存放的位置,没有就重新创建,有就在此基础上继续训练
checkpoint_save_path = 'models/checkpoint'  
if os.path.exists(checkpoint_save_path + '.index'):
    model.load_weights(checkpoint_save_path)  
cp_callback = tf.keras.callbacks.ModelCheckpoint(filepath=checkpoint_save_path,
                                             save_weights_only=True,
                                             save_best_only=True)
# 训练模型
model.fit(train_ds,validation_data=val_ds,epochs=100, callbacks=[cp_callback])
复制代码

这段程序运行之后,会在控制台打印训练过程和进度,等不打印了,就表示训练好了。

同时,同级目录models下,也生成了对应的训练结果文件。

3.3 使用数据

训练完成之后,我们就要开始享用成功果实了。

我们在同级目录下放一个res文件夹,里面是我们需要整理的图片,此时还是乱的。

乱图.png

然后,我们构建模型,加载训练结果:

class_names = ['airconditioner', 'barbecue', 'beach', 'cicada', 'dragonboat', 'electricfan', 'icecream', 'swimwear', 'watermelon', 'zongzi']
img_width = 256
img_height = 256
num_classes = len(class_names)
model = create_model(img_height, img_width, num_classes)
model.load_weights('models/checkpoint')
复制代码

再然后,我们读入res文件下的图片,然后把它们交给model.predict进行识别:

output_folder = "res"
all_files=os.listdir(output_folder)
imgs = [] 
for f_name in all_files:
    if not f_name.endswith(".jpg"):
        continue
    img_path = output_folder+"/"+f_name
    try:
        im = cv2.imread(img_path)
        im = cv2.cvtColor(im,cv2.COLOR_BGR2RGB)
        im = cv2.resize(im,(img_width,img_height))
        imgs.append(im)
    except Exception as e:
        print(f_name, str(e))
tf_imgs = tf.convert_to_tensor(imgs,dtype=tf.int64)

# 利用模型预测分类
predicts = model.predict(tf_imgs) 
for (i,predict) in enumerate(predicts): 
    index = np.argmax(predict) 
    result_label = class_names[int(index)]
    print(all_files[i],"is", result_label)
    old_file_path = output_folder+"/"+ all_files[i]
    new_file_dir = output_folder+"/"+ result_label
    if not os.path.exists(new_file_dir):
        os.makedirs(new_file_dir)
    #移动文件到指定文件夹
    shutil.move(old_file_path, new_file_dir)
复制代码

运行之后,它会将某一个文件移入到识别的分类文件夹中,就像下面这样: 移动图片.gif

以后,再有类似的夏天图片,你就可以让程序替你整理了。

当然,如果你有新的分类,比如:遮阳帽,也记得增加这个分类的训练,否则计算机是无法识别的哦。

四、总结问题

图片标记器只是一个辅助手段,是额外的工具。如果只看用人工智能进行图片分类的话,从头到尾用几十行代码就可以完全搞定了。

他复杂吗?以前复杂,现在不复杂。他难吗?以前很难,现在不难。

人工智能的发展,实际上远远超过你我的想象和认知,我们需要去了解它,应用它。

对于我,我更愿意去传播它。我正是发现了人工智能技术变得如此简单,才将首页介绍改为:TF男孩,婴幼儿学习TensorFlow框架的推广大使。我很希望中国的人工智能可以从娃娃抓起。用科技改变生活,是每个技术人的追求和使命。

最后借用掘金APP闪屏页的标语结束本文:相信技术,传递价值。

收藏成功!
已添加到「」, 点击更改