狗罩佩戴自动识别算法的设计与实现

一. 设计任务说明

1.1 主要设计内容

1.1.1 设计并实现狗有无佩戴狗嘴套识别算法,基本功能要求

  1. 对目标进行图片数据收集。
  2. 进行图片处理
  3. 构建模型
  4. 对模型进行评估
  5. 要求提供较友好的用户接口,可以对新的图片导入到系统中进行处理,并将结果返回给用户。
  6. 要求最后的产品进行良好的封装,方便后续其他任务图像分类任务调用和扩展(基于百度图片 + 深度学习的分类任务)

1.2 开发该系统软件环境及使用的技术说明

Python: 3.7.16
Sublime Text: 4126
TensorFlow: 2.5.0
爬虫代码函数运用到的包:

#!/usr/bin/python3
# encoding=utf-8
from fake_useragent import UserAgent
import requests
import re
import os

深度学习网络运用到的包:

import os
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '1'
os.environ['TF_XLA_FLAVGS'] = '--TF_xla_enable_xla_devices'
import tensorflow as tf
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from tensorflow.python.keras.utils import np_utils
from tensorflow.python.keras.models import Sequential
from tensorflow.python.keras.layers import Convolution2D, Flatten, Dropout, MaxPooling2D, Dense, Activation
from tensorflow.keras.optimizers import SGD, Adam
from tensorflow.keras.models import load_model
import matplotlib.image as processimage
import matplotlib.pyplot as plt
from PIL import Image

二. 获取数据集

2.1 使用爬虫爬取百度照片

2.1.1 百度图片爬虫分析

从百度图片接口,提交关键词,将返回的图片爬取并保存。
爬虫思路:
百度图片的网页是一个动态网页,通过不断的对API传入新的参数可以获得新的图片数据,而每批次都含有一定的图片数据。因此可通过嵌套两个循环,爬取关键词百度图片。
循环一: 向百度图片接口传入给定参数,因此通过for循环,可以调取到想要的照片数量。
循环二: 每一次百度图片API返回的数据中,含有一定数量的关键词图片,通过对数据的for循环遍历,爬取关键词图片并保存到本地。

2.1.2 百度图片爬虫代码实现

#!/usr/bin/python3
# encoding=utf-8
from fake_useragent import UserAgent
import requests
import re
import os

headers = {'User-agent': UserAgent().random,
           "Accept-Encoding": "gzip,deflate,br",
           "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
           "Connection": "keep-alive"}

img_re = re.compile('"thumbURL":"(.*?)"')
img_format = re.compile("f=(.*).*?w")


def file_op(father_file, img, dog_class, picture_name):
    foldername = os.path.join(father_file, dog_class)
    tmp_file_name = f'{foldername}/{picture_name}.jpg'
    if not os.path.exists(foldername):
        os.makedirs(foldername)
    with open(file=tmp_file_name, mode='wb') as file:
        try:
            print("it works")
            file.write(img)  # 因为这里已经隐式的进行数据输出,所以即使没有return函数也没有关系

        except:
            print('found it')
            pass


def xhr_url(father_file, url_xhr, text_, start_num=0, page=5):
    end_num = page * 30
    picture_num = 0
    picture_name = f'P_{picture_num}'
#--------------------循环一------------------
    for page_num in range(start_num, end_num, 30):
        resp = requests.get(url=url_xhr + str(page_num), headers=headers)
        if resp.status_code == 200:
            img_url_list = img_re.findall(resp.text)  # 这是个列表形式
            #print(img_url_list)
            #------------------循环二----------------------
            for img_url in img_url_list:
                try:  # 直接跳过数据错误的乱码
                    img_url = img_url.replace("\\", "")
                    print(img_url)
                    img_rsp = requests.get(url=img_url, headers=headers)
                    file_op(father_file = father_file, img=img_rsp.content, dog_class=text_,
                            picture_name=picture_name)
                    print("{} 下载完毕".format(picture_name))
                    picture_num += 1
                    picture_name = f'P_{picture_num}'
                except:
                    print("遇到了一个错误的图片url")
                    continue
        else:
            print("遇到了网页反爬")
            break
    print("内容已经完全爬取")


if __name__ == "__main__":
	father_file = input('分类照片将放置在: ')
    text_ = input("输入你想检索内容:")
    # 学习参数批量导入
    org_url = "https://image.baidu.com/search/index?ct=201326592&tn=baiduimage&word={}&pn=".format(
        text_)
    url_xhr = org_url
    start_num = int(input("开始页:")) # 建议为0
    page = int(input("所需爬取页数:")) # 照片最终数量可通过 30*page 初略估计
    xhr_url(father_file, org_url, text_, start_num, page)

2.1.3 爬取关键词图片

关键词1: 狗套、狗嘴套
关键词2: 狗的照片

cd <爬虫代码文件父亲目录>
python dog_scrap.py
# 以下内容由python的input函数实现交互,请填入尖括号内的信息
分类照片将放置在:<提供一个文件地址>
输入你想检索内容:<关键词1>
开始页:<0>
所需爬取页数:<30>
输入你想检索内容:<关键词2>
开始页:<0>
所需爬取页数:<30>

爬取过程
爬取结果示意
最后我们将获得两个分别以两个关键词命名的文件夹,文件夹中有相应关键词的百度图片搜索结果。

2.2 对照片的初步筛选

2.2.1 照片剔除规则

哈哈哈哈哈,你以为对照片的筛选会是使用代码???
不可能,还得自己来手动筛选!!!
对关键词1文件夹:1. 只有狗嘴套但是没有狗的照片、2.没有狗嘴套的照片(如帐篷、狗绳等)
对关键词2文件夹:1.不是狗的照片、2.狗带有嘴套的照片

2.2.2 使用图片缩略图功能

一张张点开图片很麻烦,建议打开windons的图片缩略图功能,大概5分钟内能够处理完图片数据。
打开图片缩略图
打开【文件所在文件夹】 > 点击【查看】> 点击最右边的【选项】> 进入到【文件夹选项】> 点击 【查看】> 去掉勾选【始终显示图标,从不显示缩略图】> 点击【应用】
在这里插入图片描述

三. 图片格式转深度学习数据格式

3.1 JPG图片读取为np.array格式

.jpg -----> np.array
这部分是cv学习中的经典图片数据处理方式
关键点1: 读取图片
关键点2: 将jpg格式装换为np.array格式

# 统一尺寸
# 从矩阵角度看照片:每一个像素是0-255之间的数, 为100*100*3的矩阵,
# 彩色图片为3维数据,前两维为图片的大小,后一维为像素颜色
def read_generalize(img_path):
    img = Image.open(img_path)
    new_img = img.convert('RGB').resize((100, 100), Image.BILINEAR)
    #img = Image.open(filename).convert('RGB')
    #new_img.save(img_path, jgpfile)
    # return np.array(img)
    return np.array(new_img)

read_generalize函数的参数是一个图片的目录地址,输出是这个图片RGB模式下经过100 x 100大小调整后的 数据矩阵。
可以调整的参数为 resize(100,100) , 通过变换resize里面的照片长、照片宽,从而调整输入模型的矩阵形状,需要注意的是,如果resize调整了,后期的模型的输入也应该相应地调整。

3.2 循环读取文件夹中的所有照片

通过调用3.1中的代码函数 read_generalize函数,并结合一定的循环,可以读取文件夹中的所有目标图片。
文件夹示例
在狗套识别文件夹中,共包含两个分类文件夹:mask文件夹、nomask文件夹
所以在data_build中使用了两个遍历for来读取所有的照片数据
**循环一:**读取两个文件夹
**循环二:**对没个子文件夹,遍历其内部的所有图片

def data_build(path):
    # 在path文件下,应该是不同狗的品种的分类文件夹
    class_file = []
    # os.listdir:参数为文件夹路径,可以返回文件夹下的所有子文件夹、文件名称。
    for file_name in os.listdir(path):
        print(f"正在提取文件:{file_name}")
        class_file.append(file_name)  # 不同狗的文件夹
    N_CLASSES = len(class_file)
    # 将不同狗品种文件夹下的图片提取出来,并打上标签
    X, y = [], []
    for file in class_file:
        class_name = file.split('-')[-1]
        father_root = os.path.join(path, file)
        for picture in os.listdir(father_root):
            picture = read_generalize(os.path.join(father_root, picture))
            X.append(picture)
            y.append(class_name)
    X = np.array(X).astype('float32')
    # 将分类变量转化为独热变量https://blog.csdn.net/Arctic_Beacon/article/details/85292994
    y = pd.DataFrame({'class_': y})
    print(y)
    y = pd.get_dummies(y)
    y_columns = list(y.columns)
    # y = nputils.to_categorical(y, N_CLASSES)
    print("数据中的分类标签:\n", y)
    print(y.describe().T)
    X /= 255  # np矩阵的广播性会对所有数字进行/=运算
    return X, y, y_columns

data_build函数接受一个文件夹目录(该文件夹中应有不同图片类别的子文件夹,子文件夹中是相应的类别照片),返回3个变量。
变量1: 以列表为最外层容器的变量X,列表中是该文件夹目录下的所有照片的矩阵形式。
变量2: 关于照片类别的独热数据,为dataframe格式,y,(行,列)=(n, k)。
变量3: y_columns,是一个类别,里面是对应的类别 。

四. 深度学习网络构建

通过tensorflow内置的keras函数搭建深度学习网络。
卷积神经网络共有2层, 全连接层有5层
CNN 1层的卷积核尺寸为(5,5),边距处理方法为 ‘same’, 相应的输入数据维度设置为:
(100,100,3)。
通过叠加CNN 2层,加深网络的深度,使模型能够学习地更细致。
全连接层 共有5层,相应为:
Dense(1024)、Dense(512)、Dense(256)、Dense(20)、 Dense(2)
通过叠加一定数量的全连接层,能够让深度学习网络将学习到的知识具化成相应的类别。
####对激活函数进行说明,以及输入和输出的参数调整

def model_build(X, y):
    #-- 创建CNN神经网络
    model = Sequential()
    # CNN 1层
    model.add(Convolution2D(
        filters=32,  # Output  (100,100,32)
        kernel_size=(5, 5),  # 卷积核
        padding='same',  # 边距处理方法 padding method
        input_shape=(100, 100, 3),  # input shape
    ))
    model.add(Activation('relu'))
    model.add(MaxPooling2D(
        pool_size=(2, 2),  # Output  (50,50,32)
        strides=(2, 2),
        padding='same',
    ))
    # CNN 2层
    model.add(Convolution2D(
        filters=64,  # Output (50,50,64)
        kernel_size=(2, 2),
        padding='same',
    ))
    model.add(Activation('relu'))
    model.add(MaxPooling2D(  # Output(25,25,64)
        pool_size=(2, 2),
        strides=(2, 2),
        padding='same',
    ))
    # 全连层 Layer -1
    model.add(Flatten())
    model.add(Dense(1024))
    model.add(Activation('relu'))
    # 全连层 Layer -2
    model.add(Dense(512))
    model.add(Activation('relu'))
    # 全连层 Layer -3
    model.add(Dense(256))
    model.add(Activation('relu'))
    # 全连层 Layer -3
    model.add(Dense(20))
    model.add(Activation('relu'))
    # 全连层 Layer -4
    model.add(Dense(2))  # 添加120个节点的连接层
    model.add(Activation('softmax'))  # 添加激活层,softmax将输入的一个向量转换为概率分布
    # 定义优化器
    adam = Adam(lr=0.0001)
    # 编译模型
    model.compile(
        optimizer=adam,
        loss="categorical_crossentropy",
        metrics=['accuracy']
    )
    # 描绘模型
    model.summary()
    # 一共进行32论
    # 也就是说840张图片,每次训练10张,相当于一共训练84次
    model.fit(X, y, batch_size=200, epochs=50, verbose=1)  # verbose带进度条的日志信息
    # 保存模型权重
    model.save_weights('./weights.h5', overwrite=True)
    # 评估模型
    #score = model.evaluate(x_test, y_test, batch_size=10)
    # print(score)
    return model

五. 预测与评估

5.1 数据切分函数

对训练集和测试集的切分,简单的调用了sklearn 的train_test_split函数,花费的时间比较长。返回的结果为X_train(训练数据X), X_test(测试数据X), y_train(标签训练数据y), y_test(标签测试数据y)。
代码如下:

def data_split(X, y):
    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=3, shuffle=True)
    return X_train, X_test, y_train, y_test

5.2 模型评估函数

为了检验数据实际的泛化能力,应避免使用训练集数据来评估模型的精确度,因为这样不能够防止可能存在的过拟合对精确度影响。
使用之前train_test_split函数切分出的测试集数据 X_test, y_test,对模型进行评估。
代码如下:

def model_show(X_test, y_test, target, model_save_path, model=False):
# --------------使用测试集的数据对模型进行评估-----------------
    # X_test 和 y_test 用于进行评估的测试集
    # target 应该为y_columns, 是一个列表
    # model_save_path: 模型保存的目录地址,扩展名为.h5
    # model: 通过model函数进行模型继承
    #path_for_pred = input('上传图片的文件目录: ')
    score = model.evaluate(X_test, y_test, batch_size=100)
    print("模型的LOSS是:{},accuracy是:{}".format(score[0],format[1]))
# --------------这是一个用来简答测试模型效果的照片--------------
    path_for_pred = '这里应该调用一个非样本的数据照片的文件目录:如D:\zhanshi.jpg'
    # 预处理图片
    X_pred = [read_generalize(path_for_pred)]
    X_pred = np.array(X_pred).astype('float32')
    X_pred /= 255
    # 加载权重文件
    if model == False:
        model.load_weights(model_save_path)
    # 预测类别
    classes = model.predict(X_pred)[0]
    #classes_index = classes.index(1)
    classes_index = np.argmax(classes)  # 或者使用np.argmax(classes)来获取概率最高的元素的索引
    print("识别结果为:" + target[classes_index])

在划分线一下的代码的功能就是使用测试集数据评估训练集训练出来的模型,evaluate()函数返回的是一个包含两个元素列表[元素1,元素2],其中元素1表示了在评估过程中的损失LOSS,元素2表示了模型在测试集数据基础上的ACCURACY。
下图为模型评估结果:
模型评估结果
可以看到,训练该模型使用了大约8分钟,模型的最终的得分(accuracy) 为0.88,还是能够让人满意的。
训练过程模型在第26次迭代后,模型的精确度稳定在0.99。

六. 交互界面

(注:仍在学习pyqt5中,敬请期待,后续填坑)

七、最终代码

狗罩识别总代码

import os
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '1'
os.environ['TF_XLA_FLAVGS'] = '--TF_xla_enable_xla_devices'
import tensorflow as tf
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from tensorflow.python.keras.utils import np_utils
from tensorflow.python.keras.models import Sequential
from tensorflow.python.keras.layers import Convolution2D, Flatten, Dropout, MaxPooling2D, Dense, Activation
from tensorflow.keras.optimizers import SGD, Adam
from tensorflow.keras.models import load_model
import matplotlib.image as processimage
import matplotlib.pyplot as plt
from PIL import Image


# 统一尺寸
# 从矩阵角度看照片:每一个像素是0-255之间的数, 为100*100*3的矩阵,
# 彩色图片为3维数据,前两维为图片的大小,后一维为像素颜色
def read_generalize(img_path):
    img = Image.open(img_path)
    new_img = img.convert('RGB').resize((100, 100), Image.BILINEAR)
    #img = Image.open(filename).convert('RGB')
    #new_img.save(img_path, jgpfile)
    # return np.array(img)
    return np.array(new_img)


def data_build(path):
    # 在path文件下,应该是不同狗的品种的分类文件夹
    class_file = []
    # os.listdir:参数为文件夹路径,可以返回文件夹下的所有子文件夹、文件名称。
    for file_name in os.listdir(path):
        print(f"正在提取文件:{file_name}")
        class_file.append(file_name)  # 不同狗的文件夹
    N_CLASSES = len(class_file)
    # 将不同狗品种文件夹下的图片提取出来,并打上标签
    X, y = [], []
    for file in class_file:
        class_name = file.split('-')[-1]
        father_root = os.path.join(path, file)
        for picture in os.listdir(father_root):
            picture = read_generalize(os.path.join(father_root, picture))
            X.append(picture)
            y.append(class_name)
    X = np.array(X).astype('float32')
    # 将分类变量转化为独热变量https://blog.csdn.net/Arctic_Beacon/article/details/85292994
    y = pd.DataFrame({'class_': y})
    print(y)
    y = pd.get_dummies(y)
    y_columns = list(y.columns)
    # y = nputils.to_categorical(y, N_CLASSES)
    print("数据中的分类标签:\n", y)
    print(y.describe().T)
    X /= 255  # np举证的广播性会对所有数字进行/=,但是前3维是通过最后一位在举证中的位置表示的,所以不用担心
    return X, y, y_columns


# 搭建卷积神经网络
'''Keras是基于TensorFlow的深度学习库,是由纯Python编写而成的高层神经网络API,也仅支持Python开发。
它是为了支持快速实践而对Tensorflow的再次封装,
让我们可以不用关注过多的底层细节,能够把想法快速转换为结果。
'''
# 搭建卷积层,共有32个卷积核,卷积核大小为3*3,采用relu的激活方式
# input_shape 表示输入数据的维度
# 序贯模型,如同搭积木一样,可以在神经网络上不断叠加层数
# 搭建模型


def data_split(X, y):
    X_train, X_test, y_train, y_test = train_test_split(
        X, y, test_size=0.2, random_state=3, shuffle=True)
    return X_train, X_test, y_train, y_test


def model_build(X, y):
    #-- 创建CNN神经网络
    model = Sequential()
    # CNN 1层
    model.add(Convolution2D(
        filters=32,  # Output  (100,100,32)
        kernel_size=(5, 5),  # 卷积核
        padding='same',  # 边距处理方法 padding method
        input_shape=(100, 100, 3),  # input shape
    ))
    model.add(Activation('relu'))
    model.add(MaxPooling2D(
        pool_size=(2, 2),  # Output  (50,50,32)
        strides=(2, 2),
        padding='same',
    ))
    # CNN 2层
    model.add(Convolution2D(
        filters=64,  # Output (50,50,64)
        kernel_size=(2, 2),
        padding='same',
    ))
    model.add(Activation('relu'))
    model.add(MaxPooling2D(  # Output(25,25,64)
        pool_size=(2, 2),
        strides=(2, 2),
        padding='same',
    ))
    # 全连层 Layer -1
    model.add(Flatten())
    model.add(Dense(1024))
    model.add(Activation('relu'))
    # 全连层 Layer -2
    model.add(Dense(512))
    model.add(Activation('relu'))
    # 全连层 Layer -3
    model.add(Dense(256))
    model.add(Activation('relu'))
    # 全连层 Layer -4
    model.add(Dense(20))
    model.add(Activation('relu'))
    # 全连层 Layer -5
    model.add(Dense(2))  # 添加120个节点的连接层
    model.add(Activation('softmax'))  # 添加激活层,softmax将输入的一个向量转换为概率分布
    # 定义优化器
    adam = Adam(lr=0.0001)
    # 编译模型
    model.compile(
        optimizer=adam,
        loss="categorical_crossentropy",
        metrics=['accuracy']
    )
    # 描绘模型
    model.summary()
    model.fit(X, y, batch_size=200, epochs=35, verbose=1)  # verbose带进度条的日志信息
    # 保存模型权重
    model.save_weights('./weights.h5', overwrite=True)
    return model


def model_show(X_test, y_test, target, model_save_path, model=False):
    # X_test 和 y_test 用于进行评估的测试集
    # target 应该为y_columns, 是一个列表
    # model_save_path: 模型保存的目录地址,扩展名为.h5
    # model: 通过model函数进行模型继承
    #path_for_pred = input('上传图片的文件目录: ')
    score = model.evaluate(X_test, y_test, batch_size=100)
    print("模型最终得分是:{}".format(score[1]))
    # 这是一个用来简答测试模型效果的照片
    path_for_pred = 'D:\\dog_picture\\金毛\\P_0.jpg'
    # 预处理图片
    X_pred = [read_generalize(path_for_pred)]
    X_pred = np.array(X_pred).astype('float32')
    X_pred /= 255
    # 加载权重文件
    if model == False:
        model.load_weights(model_save_path)
    # 预测类别
    classes = model.predict(X_pred)[0]
    #classes_index = classes.index(1)
    classes_index = np.argmax(classes)  # 或者使用np.argmax(classes)来获取概率最高的元素的索引
    print("识别结果为:" + target[classes_index])


def main():
    picture_path = 'D:\\dog_picture\\狗套识别'
    model_save_path = 'C:\\Users\\YX560\\Desktop\\模拟训练\\小狗狗罩\\weights.h5'
    X, y, y_columns = data_build(picture_path)
    X_train, X_test, y_train, y_test = data_split(X, y)
    model = model_build(X_train, y_train)
    print("********************模型训练完毕********************")
    model_show(X_test, y_test, y_columns, model_save_path=False, model=model)


# tensorflow 需要下载keras吗
# 如果需要下载,那么TensorFlow与keras之间的版本处理
# 进一步地,有无其他成熟的包可以直接进行深度学习使用。
# pytorch环境安装(这个没有这么急)

if __name__ == '__main__':
    main()

八. 项目总结

本项目通过百度爬虫实现对有佩戴狗罩和无配戴狗罩的图片收集,使用tensorflow搭建深度学习网络,获得了精确度Accuray为0.88的图片分类模型。

项目优点:

  1. 逻辑简单,可扩展。通过对不同类型的照片爬取,和模型调用,该项目能够较方便的迁移到其他分类问题:如猫狗识别,狗的品种等等

不足之处:
2. 目前模型的精确还有可提升的空间,这与数据集质量存在一定的关系。在爬取百度图片中,发现存在大量相同或相似的图片,并且混杂一些无关的照片,需要手动处理。此外可通过扩展图片来源,如增加国外图片数据。从而从图片质量和数量上提升模型的精确度。
3. 使用的深度模型较为简单。无法实现具体目标的打框实现,后续笔者会继续学习YOLO等相关图像识别算法,从而提升模型能力。

写在最后: 在搭建模型过程中,由于没有合理借鉴其他项目的模型搭建结构,导致本项目前期的模型一直处于较低的精确度。但是研究学习了一下后,模型的精确度大大提升。所以学习前人的经验是很重要的。未来是美好的,道路是曲折的。 同学们共勉

参考资料

  1. 百度图片
    https://image.baidu.com/
  2. python爬取百度图片的思路与代码(最后附上了代码)
    https://blog.csdn.net/weixin_52971139/article/details/125065788
  3. 【OpenCV】车牌自动识别算法的设计与实现
    https://blog.csdn.net/m0_58847451/article/details/129311520
  4. 【python机器学习课程设计】狗的品种识别
    https://www.cnblogs.com/wwwwpoor/p/16999884.html
  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值