【CV项目实战】纯新手如何从零开始完成一个工业级的图像分类任务?

工业级图像分类实战:从模型训练到部署的完整闭环 图像分类是计算机视觉最基础且应用最广的任务之一,其核心在于构建鲁棒、可复现、可部署的端到端流程。理解卷积神经网络的底层原理(如padding机制、特征图尺寸变化)是调优前提;掌握tf.data高效数据流水线与BatchNormalization正确位置等硬核细节,直接决定训练稳定性与GPU利用率。迁移学习微调、Focal Loss处理类别不平衡、CosineAnnealing学习率调度等技术,显著提升小样本场景下的泛化能力。而模型压缩(INT8量化)、确定性训练(四重随机种子控制)、SavedModel跨平 阅读详情

大家好,欢迎来到专栏《CV项目实战》,在这个专栏中我们会讲述计算机视觉相关的项目实战,有大型的完整项目,也有精炼的核心算法实战。

本次主要讲述一个完整的工业级别图像分类项目的标准流程,涉及环境配置,数据准备,模型定义,模型训练,模型测试。

作者&编辑 | 言有三

本文资源与项目结果展示

本文篇幅:7900字

背景要求:会使用Python和任一深度学习开源框架

附带资料:代码一份,版本包括Pytorch+Tensorflow+Caffe

同步平台:有三AI知识星球(一周内)

1 项目背景

对于很多初入深度学习计算机视觉领域的朋友来说,当前开源资料非常多,但有时候难以适从,其中很多资料都没有包含完整的项目流程,而只是对某个流程的部分截取,对能力的锻炼不够。

图像分类是整个计算机视觉领域中最基础的任务,也是最重要的任务之一,最适合拿来进行学习实践。为了让新手们能够一次性体验一个工业级别的图像分类任务的完整流程,本次我们选择带领大家完成一个对视频中人脸进行表情识别的任务。人脸表情识别(facial expression recognition, FER)作为人脸识别技术中的一个重要组成部分,近年来在人机交互、安全、机器人制造、自动化、医疗、通信和驾驶领域得到了广泛的关注,成为学术界和工业界的研究热点,是人脸属性分析的重点。

2 准备工作

本次项目开发需要以下环境:

(1) Linux系统,推荐ubuntu16.04或者ubuntu18.04。使用windows系统也可以完成,但是使用Linux效率更高。

(2) 最好拥有一块显存不低于6G的GPU显卡,如果没有使用CPU进行训练速度较慢。

(3) 安装好的Tensorflow或者其他开源框架(caffe,pytorch等)

为什么要选择Linux就不多说了,公司开发都是使用Linux服务器,个人开发者机器常常会安装windows与Linux双系统。双系统的安装每个人的个人电脑环境不同,一定是会有差异的,只有安装过程中才会知道,推荐的双系统安装博客教程:

https://www.zhihu.com/question/34611974/answer/373037398,如果遇到了任何问题,可以自行百度解决,总的流程是:

(1) 去ubuntu官网下载ubuntu系统镜像16.04或者18.04。

(2) 制作U盘启动盘。

(3) 使用启动盘进行安装,安装过程中选择好分区大小。

(4) 安装完之后即可进入系统。

(5) Linux系统初学者需要掌握一些基本命令。

3 数据获取

很多实际项目我们不会有现成的数据集,虽然可以通过开源数据集获取,但是我们还是要学会自己从零开始获取和整理。下面讲述如何准备好本次项目所需要的数据集,包括以下部分。

(1) 学会使用爬虫爬取图像。

(2) 对获得的图片数据进行整理,包括重命名,格式统一。

(3) 利用人脸检测算法删选出有用的样本,利用关键点检测算法裁剪出用于算法训练的嘴唇区域。

3.1 数据爬取

由于没有直接对应的开源数据集,或者开源数据集中的数据比较少,尤其是对于嘟嘴,大笑等类的数据,而搜索引擎上有海量数据,所以我们可以从中爬取。下面开始讲述具体的步骤,我们的任务是一个表情分类任务,因此需要爬取相关图片,包括嘟嘴,微笑,大笑等表情。

参考有三AI公众号的爬虫文章,文章链接如下:

【杂谈】深度学习必备,各路免费爬虫一举拿下

爬虫的GUI界面如下:

我们对百度搜索引擎,自行定义搜索词(比如嘟嘴),爬取图片结果如下:

自此就可以获得数千张图像,然后进行第二步,数据整理。

3.2 数据整理

爬取得到的数据是比较脏的,需要进行整理,主要包括统一图片后缀和重命名。

统一后缀格式可以减少以后写数据API时的压力,也可以测试图片是不是可以正常的读取,及时防止未知问题的出现,这很重要。

首先我们看下爬取完毕的数据有多少种数据格式。

总共1364张图,可以看到总有jpeg,bmp,png格式,我们首先将其全部转换为jpg格式,这也是所有框架支持的格式,格式转换代码如下:

import os

import sys

import cv2

import numpy as np


def listfiles(rootDir):

    list_dirs = os.walk(rootDir)

    for root, dirs, files in list_dirs:

        for d in dirs:

            print os.path.join(root,d)

        for f in files:


            fileid = f.split('.')[0] 


            filepath = os.path.join(root,f)

            try:

                src = cv2.imread(filepath,1)

                print "src=",filepath,src.shape

                os.remove(filepath)                          cv2.imwrite(os.path.join(root,fileid+".jpg"),src)

            except:

                os.remove(filepath)

                continue


listfiles(sys.argv[1]) ##输入文件夹即可

统一格式为jpg之后预览如下:

另外,在刚才的脚本中我们可以加上一个计数器完成重命名,不过因为这里爬虫已经完成了这个重命名操作,我们就略去。

3.3 数据清洗

利用搜索引擎爬取得到的图片肯定有不符合要求的,数据清洗主要是删除不合适的图片,即非人脸的照片。

可以采用肉眼观察的方式,也可以利用程序进行筛选,我们调用opencv的人脸检测算法进行筛选,代码如下:

#coding:utf8

import cv2

import dlib

import numpy as np

import sys

import os

cascade_path='haarcascade_frontalface_default.xml'

cascade = cv2.CascadeClassifier(cascade_path)



images = os.listdir(sys.argv[1])

for image in images:

    im=cv2.imread(os.path.join(sys.argv[1],image),1)

    rects = cascade.detectMultiScale(im, 1.3,5)

    print "detected face",len(rects)

    if len(rects) == 0:

        cv2.namedWindow('Result',0)

        cv2.imshow('Result',im)

        os.remove(os.path.join(sys.argv[1],image))

        k =cv2.waitKey(0)

        if k == ord('q'):

            break

这个人脸检测算法是传统算法,召回率不高,因此会有一些好样本被删除。

 

最后剩下732张样本,可以看到都是比较好的样本了,后面提取人脸关键点也会简单很多。

如果利用人脸检测算法仍然无法清除干净样本,则需要手动筛选。

当然如果你使用多个关键词或者使用不同的搜索引擎同样的关键词,或者从视频中提取图片,那么爬取回来的图片很可能有重复或者非常的相似,这样的数据集需要去重,我们以后再讲。

3.4 提取嘴唇区域

接下来我们要将样本处理成我们真正训练所需要的图像,本任务只对嘴唇部分的表情进行识别,我们利用Opencv+Dlib算法提取嘴唇区域,Dlib算法会得到面部的68个关键点,我们从中得到嘴唇区域,并适当扩大。

import cv2

import dlib

import numpy as np

import sys

import os

PREDICTOR_PATH = "shape_predictor_68_face_landmarks.dat"

predictor = dlib.shape_predictor(PREDICTOR_PATH)

cascade_path='haarcascade_frontalface_default.xml'

cascade = cv2.CascadeClassifier(cascade_path)



def get_landmarks(im):

    rects = cascade.detectMultiScale(im, 1.3,5)

    x,y,w,h =rects[0]

    rect=dlib.rectangle(x,y,x+w,y+h)

    return np.matrix([[p.x, p.y] for p in predictor(im, rect).parts()])



def annotate_landmarks(im, landmarks):

    im = im.copy()

    for idx, point in enumerate(landmarks):

        pos = (point[0, 0], point[0, 1])

        cv2.putText(im, str(idx), pos,

                    fontFace=cv2.FONT_HERSHEY_SCRIPT_SIMPLEX,

                    fontScale=0.4,

                    color=(0, 0, 255))

        cv2.circle(im, pos, 5, color=(0, 255, 255))

    return im



def getlipfromimage(im,landmarks):

    xmin = 10000

    xmax = 0

    ymin = 10000

    ymax = 0



    for i in range(48,67):

        x = landmarks[i,0]

        y = landmarks[i,1]

        if x < xmin:

            xmin = x

        if x > xmax:

            xmax = x

        if y < ymin:

            ymin = y

        if y > ymax:

            ymax = y



    print "xmin=",xmin

    print "xmax=",xmax

    print "ymin=",ymin

    print "ymax=",ymax



    roiwidth = xmax - xmin

    roiheight = ymax - ymin



    roi = im[ymin:ymax,xmin:xmax,0:3]



    if roiwidth > roiheight:

        dstlen = 1.5*roiwidth

    else:

        dstlen = 1.5*roiheight



    diff_xlen = dstlen - roiwidth

    diff_ylen = dstlen - roiheight



    newx = xmin

    newy = ymin



    imagerows,imagecols,channel = im.shape

    if newx >= diff_xlen/2 and newx + roiwidth + diff_xlen/2 < imagecols:

        newx  = newx - diff_xlen/2;

    elif newx < diff_xlen/2:

        newx = 0;

    else:

        newx =  imagecols - dstlen;



    if newy >= diff_ylen/2 and newy + roiheight + diff_ylen/2 < imagerows:

        newy  = newy - diff_ylen/2;

    elif newy < diff_ylen/2:

        newy = 0;

    else:

        newy =  imagerows - dstlen;



    roi = im[int(newy):int(newy+dstlen),int(newx):int(newx+dstlen),0:3]

    return roi



def listfiles(rootDir):

    list_dirs = os.walk(rootDir)

    for root, dirs, files in list_dirs:

        for d in dirs:

            print os.path.join(root,d)

        for f in files:

            fileid = f.split('.')[0]



            filepath = os.path.join(root,f)

            try:

                im = cv2.imread(filepath,1)

        landmarks = get_landmarks(im)

                roi = getlipfromimage(im,landmarks)

                roipath = filepath.replace('.jpg','_mouth.png')

                cv2.imwrite(roipath,roi)

            except:

                print "error"

                continue



listfiles(sys.argv[1])

结果如下,自此就完成了自建数据集操作。

4 模型训练

得到了数据之后,接下来咱们使用TensorFlow 这个框架来进行模型的训练,当然了,你也可以选择使用Caffe,Pytorch等框架,文末我们会提供代码获取方法。

整个训练流程包括数据接口准备、模型定义、结果保存与分析。

4.1数据接口准备

TensorFlow中有多种数据集接口,我们首先把不同类的图像放在不同的文件夹下面,然后生成一个txt文件,它的每一行的格式是image、labelid,使用的genelist.py脚本如下:

import os

import sys

def listfiles(rootDir,txtfile,label=0):

    ftxtfile = open(txtfile,'w')

    list_dirs = os.walk(rootDir)

    count = 0

    dircount = 0

    for root, dirs, files in list_dirs:

        for d in dirs:

            print os.path.join(root,d)

            dircount = dircount + 1

        for f in files:

            print os.path.join(root,f)

            ftxtfile.write(os.path.join(root,f)+' '+str(label)+'\n')

            count = count + 1

    print rootDir+"has"+str(count)+"files"



if __name__ == '__main__':

    listfiles('../../../../../datas/mouth/0','mouth0.txt',0)

    listfiles('../../../../../datas/mouth/1','mouth1.txt',1)

上面脚本中的函数,输入一个文件夹,输出txt文件以及标签,于是所有的图片和标签就按行被准备到了一个txt文件中,如下:

../../../../datas/mouth/1/182smile.jpg 1

../../../../datas/mouth/1/435smile.jpg 1

../../../../datas/mouth/0/40neutral.jpg 0

../../../../datas/mouth/1/206smile.jpg 1

../../../../datas/mouth/0/458neutral.jpg 0

../../../../datas/mouth/0/158neutral.jpg 0

../../../../datas/mouth/1/322smile.jpg 1

../../../../datas/mouth/1/83smile.jpg 1

../../../../datas/mouth/0/403neutral.jpg 0

当然,我们还需要将其分为训练集和测试集,使用split_train_val.py,代码如下:

import sys

def splittrain_val(fileall,valratio=0.1):

    fileids = fileall.split('.')

    fileid = fileids[len(fileids)-2]

    f=open(fileall);

    ftrain=open(fileid+"_train.txt",'w');

    fval=open(fileid+"_val.txt",'w');

    count = 0

    if valratio == 0 or valratio >= 1:

        valratio = 0.1

   

    interval = (int)(1.0 / valratio)

    while 1:

        line = f.readline()

        if line:

            count = count + 1

            if count % interval == 0:

                fval.write(line)

            else:

                ftrain.write(line)

        else:

            break

splittrain_val(sys.argv[1],0.1)

当然代码有更简洁的写法,可以自己尝试。

接下来我们定义一个类,叫 imagedata,用于生成tensorflow中要使用的数据指针,代码如下dataset.py。

import tensorflow as tf

from tensorflow.contrib.data import Dataset

from tensorflow.python.framework import dtypes

from tensorflow.python.framework.ops import convert_to_tensor

import numpy as np

class ImageData:

        def read_txt_file(self):

            self.img_paths = []

            self.labels = []

            for line in open(self.txt_file, 'r'):

                items = line.split(' ')

                self.img_paths.append(items[0])

                self.labels.append(int(items[1]))

        def __init__(self, txt_file, batch_size, num_classes,

                     image_size,buffer_scale=100):

            self.image_size = image_size

            self.batch_size = batch_size

            self.txt_file = txt_file ##txt list file,stored as: imagename id

            self.num_classes = num_classes

            buffer_size = batch_size * buffer_scale



        # 读取图片

        self.read_txt_file()

        self.dataset_size = len(self.labels) 

        print "num of train datas=",self.dataset_size

        # 转换成Tensor

        self.img_paths = convert_to_tensor(self.img_paths, dtype=dtypes.string)

        self.labels = convert_to_tensor(self.labels, dtype=dtypes.int32)



        # 创建数据集

        data = Dataset.from_tensor_slices((self.img_paths, self.labels))

        print "data type=",type(data)

        data = data.map(self.parse_function)

        data = data.repeat(1000)

        data = data.shuffle(buffer_size=buffer_size)



        # 设置self data Batch

        self.data = data.batch(batch_size)

        print "self.data type=",type(self.data)



        def augment_dataset(self,image,size):

            distorted_image = tf.image.random_brightness(image,

                                               max_delta=63)

            distorted_image = tf.image.random_contrast(distorted_image,

                                             lower=0.2, upper=1.8)

            # Subtract off the mean and divide by the variance of the pixels.

            float_image = tf.image.per_image_standardization(distorted_image)

            return float_image



        def parse_function(self, filename, label):

            label_ = tf.one_hot(label, self.num_classes)

            img = tf.read_file(filename)

            img = tf.image.decode_jpeg(img, channels=3)

            img = tf.image.convert_image_dtype(img, dtype = tf.float32)

            img = tf.random_crop(img,[self.image_size[0],self.image_size[1],3])

            img = tf.image.random_flip_left_right(img)

            img = self.augment_dataset(img,self.image_size)

            return img, label_

下面来分析上面的代码,类是 ImageData,它包含几个函数,__init__构造函数,read_txt_file数据读取函数,parse_function数据预处理函数,augment_dataset数据增强函数。

我们直接看构造函数吧,分为几个步骤:

(1) 读取变量,文本 list 文件txt_file,批处理大小batch_size,类别数num_classes,要处理成的图片大小image_size,一个内存变量buffer_scale=100。

(2) 在获取完这些值之后,就到了read_txt_file函数。代码很简单,就是利用self.img_paths和 self.labels存储输入 txt 中的文件列表和对应的 label,这一点和 Caffe 很像了。

(3) 然后,就是分别将img_paths和 labels 转换为 Tensor,函数是convert_to_tensor,这是 Tensor 内部的数据结构。

(4) 创建dataset,Dataset.from_tensor_slices,这一步是为了将img和label合并到一个数据格式,此后我们将利用它的接口,来循环读取数据做训练。当然创建好dataset之后,我们需要给它赋值才能真正的有数据。data.map 就是数据的预处理,包括读取图片、转换格式、随机旋转等操作,可以在这里做。

data = data.repeat(1000) 是将数据复制1000份,这可以满足我们训练1000个 epochs。data = data.shuffle(buffer_size=buffer_size)就是数据shuffle了,buffer_size就是在做shuffle操作时的控制变量,内存越大,就可以用越大的值。

(5) 给selft.data赋值,我们每次训练的时候,是取一个batchsize的数据,所以 self.data = data.batch(batch_size),就是从上面创建的dataset中,一次取一个 batch的数据。

到此,数据接口就定义完毕了,接下来在训练代码中看如何使用迭代器进行数据读取就可以了。

4.2 模型定义

创建数据接口后,我们开始定义一个网络net.py。

def simpleconv3net(x):

        x_shape = tf.shape(x)

        with tf.variable_scope("conv3_net"):

            conv1 = tf.layers.conv2d(x, name="conv1", filters=12,kernel_size=[3,3], strides=(2,2), activation=tf.nn.relu,kernel_initializer=tf.contrib.layers.xavier_initializer(),bias_initializer=tf.contrib.layers.xavier_initializer())

            bn1 = tf.layers.batch_normalization(conv1, training=True, name='bn1')

            conv2 = tf.layers.conv2d(bn1, name="conv2", filters=24,kernel_size=[3,3], strides=(2,2), activation=tf.nn.relu,kernel_initializer=tf.contrib.layers.xavier_initializer(),bias_initializer=tf.contrib.layers.xavier_initializer())

            bn2 = tf.layers.batch_normalization(conv2, training=True, name='bn2')

            conv3 = tf.layers.conv2d(bn2, name="conv3", filters=48,kernel_size=[3,3], strides=(2,2), activation=tf.nn.relu,kernel_initializer=tf.contrib.layers.xavier_initializer(),bias_initializer=tf.contrib.layers.xavier_initializer())

            bn3 = tf.layers.batch_normalization(conv3, training=True, name='bn3')

            conv3_flat = tf.reshape(bn3, [-1, 5 * 5 * 48])

            dense = tf.layers.dense(inputs=conv3_flat, units=128, activation=tf.nn.relu,name="dense",kernel_initializer=tf.contrib.layers.xavier_initializer())

            logits= tf.layers.dense(inputs=dense, units=2, activation=tf.nn.relu,name="logits",kernel_initializer=tf.contrib.layers.xavier_initializer())



        return logits

上面就是我们定义的网络,是一个简单的3层卷积。在 tf.layers 下,有各种网络层,这里就用到了 tf.layers.conv2d,tf.layers.batch_normalization和 tf.layers.dense,分别是卷积层,BN 层和全连接层。我们以一个卷积层为例:

conv1 = tf.layers.conv2d(x, name="conv1", filters=12,kernel_size=[3,3], strides=(2,2), activation=tf.nn.relu,kernel_initializer=tf.contrib.layers.xavier_initializer(),bias_initializer=tf.contrib.layers.xavier_initializer())

x 即输入,name 是网络名字,filters 是卷积核数量,kernel_size即卷积核大小,strides 是卷积 stride,activation 即激活函数,kernel_initializer和bias_initializer分别是初始化方法。可见已经将激活函数整合进了卷积层,更全面的参数,请自查 API。其实网络的定义,还有其他接口,tf.nn、tf.layers、tf.contrib,各自重复,有些混乱。这里之所以用tf.layers,就是因为参数丰富,适合从头训练一个模型。

4.3 模型训练

直接上整个训练代码train.py,如下。

from dataset import *

from net import simpleconv3net

import sys

import os

import cv2

////-------1 定义一些全局变量-------////

txtfile = sys.argv[1]

batch_size = 64

num_classes = 2

image_size = (48,48)

learning_rate = 0.0001

debug=False

if __name__=="__main__":

////-------2 载入网络结构,定义损失函数,创建计算图-------////

   dataset = ImageData(txtfile,batch_size,num_classes,image_size)
   iterator = dataset.data.make_one_shot_iterator()
   dataset_size = dataset.dataset_size
   batch_images,batch_labels = iterator.get_next()
   Ylogits = simpleconv3net(batch_images)

   print "Ylogits size=",Ylogits.shape

   Y = tf.nn.softmax(Ylogits)
   cross_entropy = tf.nn.softmax_cross_entropy_with_logits(logits=Ylogits, labels=batch_labels)
   cross_entropy = tf.reduce_mean(cross_entropy)
   correct_prediction = tf.equal(tf.argmax(Y, 1), tf.argmax(batch_labels, 1))
   accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
   update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS)
   with tf.control_dependencies(update_ops):
       train_step = tf.train.AdamOptimizer(learning_rate).minimize(cross_entropy)

   saver = tf.train.Saver()
   in_steps = 100
   checkpoint_dir = 'checkpoints/'
   if not os.path.exists(checkpoint_dir):
       os.mkdir(checkpoint_dir)
   log_dir = 'logs/'
   if not os.path.exists(log_dir):
       os.mkdir(log_dir)
   summary = tf.summary.FileWriter(logdir=log_dir)
   loss_summary = tf.summary.scalar("loss", cross_entropy)
   acc_summary = tf.summary.scalar("acc", accuracy)
   image_summary = tf.summary.image("image", batch_images)
////-------3 执行会话,保存相关变量,还可以添加一些debug函数来查看中间结果-------////

 with tf.Session() as sess:  
       init = tf.global_variables_initializer()
       sess.run(init)  
       steps = 10000  
       for i in range(steps):
           _,cross_entropy_,accuracy_,batch_images_,batch_labels_,loss_summary_,acc_summary_,image_summary_ = sess.run([train_step,cross_entropy,accuracy,batch_images,batch_labels,loss_summary,acc_summary,image_summary])
           if i % in_steps == 0 :
               print i,"iterations,loss=",cross_entropy_,"acc=",accuracy_
               saver.save(sess, checkpoint_dir + 'model.ckpt', global_step=i)    
               summary.add_summary(loss_summary_, i)
               summary.add_summary(acc_summary_, i)
               summary.add_summary(image_summary_, i)

4.4 可视化

TensorFlow 很方便的一点,就是 Tensorboard 可视化。Tensorboard 的具体原理就不细说了,很简单,就是三步。

第一步,创建日志目录。

 log_dir = 'logs/'   

 if not os.path.exists(log_dir): os.mkdir(log_dir)

第二步,创建 summary 操作并分配标签,如我们要记录 loss、acc 和迭代中的图片,则创建了下面的变量:

loss_summary = tf.summary.scalar("loss", cross_entropy)acc_summary = tf.summary.scalar("acc", accuracy)image_summary = tf.summary.image("image", batch_images)

第三步,session 中记录结果,如下面代码:

_,cross_entropy_,accuracy_,batch_images_,batch_labels_,loss_summary_,acc_summary_,image_summary_ = sess.run([train_step,cross_entropy,accuracy,batch_images,batch_labels,loss_summary,acc_summary,image_summary])

查看训练过程和最终结果时使用:

tensorboard --logdir=logs

Loss 和 acc 的曲线图如下,可以知道已经收敛了。

5 模型测试

上面已经训练好了模型,我们接下来的目标,就是要用它来做推理,真正把模型用起来,下面我们载入一个图片,用模型进行测试。

import tensorflow as tf

from net import simpleconv3

import sys

import numpy as np

import cv2

import os

import dlib

PREDICTOR_PATH = "shape_predictor_68_face_landmarks.dat"

outputor = dlib.shape_predictor(PREDICTOR_PATH)

cascade_path='haarcascade_frontalface_default.xml'

cascade = cv2.CascadeClassifier(cascade_path)



def standardization(data):

    mu = np.mean(data, axis=0)

    sigma = np.std(data, axis=0)

    return (data - mu) / sigma



def get_landmarks(im):

    rects = cascade.detectMultiScale(im, 1.3,5)

    x,y,w,h =rects[0]

    rect=dlib.rectangle(x,y,x+w,y+h)

    return np.matrix([[p.x, p.y] for p in outputor(im, rect).parts()])



def annotate_landmarks(im, landmarks):

    im = im.copy()

    for idx, point in enumerate(landmarks):

        pos = (point[0, 0], point[0, 1])

        cv2.putText(im, str(idx), pos,

                    fontFace=cv2.FONT_HERSHEY_SCRIPT_SIMPLEX,

                    fontScale=0.4,

                    color=(0, 0, 255))

        cv2.circle(im, pos, 3, color=(0, 255, 255))

    return im



testsize = 48 ##测试图大小

input = tf.placeholder(tf.float32, [1,testsize,testsize,3])

output = simpleconv3(input,False) ##定义网络

output = tf.nn.softmax(output) ##网络预测结果



## 全局变量

## sys.argv[1] 权重文件

## sys.argv[2] 图像文件夹



with tf.Session() as sess:  

    init = tf.global_variables_initializer() ##初始化

    sess.run(init)  

    saver = tf.train.Saver()

    saver.restore(sess,sys.argv[1]) ##载入权重



    #一次测试一个文件

    imagepaths = os.listdir(sys.argv[2])

    for imagepath in imagepaths:

        im = cv2.imread(os.path.join(sys.argv[2],imagepath),1)

        try:

            rects = cascade.detectMultiScale(im, 1.3,5)

            x,y,w,h =rects[0]

            rect=dlib.rectangle(x,y,x+w,y+h)

            landmarks = np.matrix([[p.x, p.y] for p in outputor(im, rect).parts()])

        except:

            continue ###没有检测到人脸



        xmin = 10000

        xmax = 0

        ymin = 10000

        ymax = 0



        for i in range(48,67):

            x = landmarks[i,0]

            y = landmarks[i,1]

            if x < xmin:

                xmin = x

            if x > xmax:

                xmax = x

            if y < ymin:

                ymin = y

            if y > ymax:

                ymax = y



        roiwidth = xmax - xmin

        roiheight = ymax - ymin



        roi = im[ymin:ymax,xmin:xmax,0:3]



        if roiwidth > roiheight:

            dstlen = 1.5*roiwidth

        else:

            dstlen = 1.5*roiheight



        diff_xlen = dstlen - roiwidth

        diff_ylen = dstlen - roiheight



        newx = xmin

        newy = ymin



        imagerows,imagecols,channel = im.shape

        if newx >= diff_xlen/2 and newx + roiwidth + diff_xlen/2 < imagecols:

            newx  = newx - diff_xlen/2;

        elif newx < diff_xlen/2:

            newx = 0;

        else:

            newx =  imagecols - dstlen;



        if newy >= diff_ylen/2 and newy + roiheight + diff_ylen/2 < imagerows:

            newy  = newy - diff_ylen/2;

        elif newy < diff_ylen/2:

            newy = 0;

        else:

            newy =  imagecols - dstlen;



        roi = im[int(newy):int(newy+dstlen),int(newx):int(newx+dstlen),0:3]

        roi = cv2.cvtColor(roi,cv2.COLOR_BGR2RGB)

        roiresized = cv2.resize(roi,(testsize,testsize))

        roiresized = standardization(roiresized)

        imgs = np.zeros([1,testsize,testsize,3],dtype=np.float32)

        imgs[0:1,] = roiresized

        print(imgs.shape)

        result = sess.run(output, feed_dict={input:imgs}) ##进行预测

        ## 获得结果,并统计指标

        result = np.squeeze(result)

        print(result)

        index = np.argmax(result)



        im_show = cv2.imread(os.path.join(sys.argv[2],imagepath),1)

        im_h,im_w,im_c = im_show.shape

        pos_x = int(newx+dstlen)

        pos_y = int(newy+dstlen)

        font = cv2.FONT_HERSHEY_SIMPLEX

        cv2.rectangle(im_show,(int(newx),int(newy)),(int(newx+dstlen),int(newy+dstlen)),(0,255,255), 2)

        if index == 0:

            cv2.putText(im_show, 'none', (pos_x,pos_y), font, 1.2, (0,0,255), 1)

        else:

            cv2.putText(im_show, 'smile', (pos_x,pos_y), font, 1.2, (0,0,255), 1)

        cv2.namedWindow('result',0)

        cv2.imshow('result',im_show)

        cv2.imwrite(os.path.join('results',imagepath),im_show)

        cv2.waitKey(0) 

从上面的代码可知,与 Train 时同样,需要定义模型。然后,用 restore 函数从 saver 中载入参数,读取图像并准备好网络的格式,sess.run 就可以得到最终的结果了。如下,黄色框是我们的嘴唇区域,'none'表示没有表情。

总结

本次我们完成了一个图像分类任务项目的全部流程,读者可以将其拓展成视频的格式,如本文最开始展示的gif效果。

想要获取tensorflow/pytorch/caffe等3个框架的完整代码,请移步有三AI知识星球社区。

从‘力导图’到‘文本高亮’:实战图解SHAP如何搞定NLP情感分析与CV图像分类的可解释性 本文深入探讨了SHAP(SHapley Additive exPlanations)在NLP情感分析与CV图像分类中的可解释性应用。通过实战案例,展示了如何利用SHAP的统一解释框架,从文本高亮到热力图,揭示BERT和ResNet等复杂模型的决策逻辑,帮助开发者提升模型透明度和业务可信度。 阅读详情

相关推荐

水瓶液位图像分类:工业级CV小闭环实战指南

图像分类是计算机视觉的基础任务,其核心在于从像素中提取语义并映射到离散状态。在工业质检场景中,状态识别(如满/半/空)比通用物体识别更具落地价值,它依赖对物理特征(如液面位置、边缘结构)的鲁棒建模,而非单纯纹理匹配。本项目以水瓶液位分类为切入点,揭示小样本(327张)、强干扰(反光、畸变、遮挡)条件下的CNN建模关键:定制化预处理、Sobel初始化卷积核、分层冻结策略及Grad-CAM可解释性验证。技术价值体现在轻量模型(1.2M参数)在Jetson Nano上达23ms推理速度,且支持ONNX+Tenso

weixin_42548782的博客 294

CV在工业界的落地场景总结

计算机视觉(Computer Vision,CV)是人工智能的一个重要分支,很多技术也在工业界落地使用了,本文总结一下在生活中切实使用过的场景。

forget-tears的博客 4425

蘑菇图像分类数据集:工业级CV入门数据基座

图像分类是计算机视觉最基础且高频的应用场景,其核心挑战在于高质量、结构规范、可复现的数据集供给。本文聚焦小样本视觉任务中的数据组织范式与加载实践,深入解析 train/valid/test 三段式划分、RoboFlow 标注流水线、ImageFolder 零配置加载等关键技术环节。通过标准化预处理、类别映射校验、分布平衡策略及图像质量量化评估,显著提升模型训练稳定性与泛化能力。该方案不仅适用于蘑菇等农业细粒度识别,更可迁移至医疗影像、工业质检等需强数据可信度的落地场景,为 PyTorch 工程化训练提供开箱

weixin_33212799的博客 317

为什么现在不看好 CV 方向了呢?

作者:匿名用户 链接:https://www.zhihu.com/question/383486199/answer/1606619221 来源:知乎 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 从一个cv内卷失败者的角度来聊一下。cv难的地方主要包含创新算法,模型底层加速和部署。前者就是各种大佬,既能够提出好的idea,又能够解决实现idea的各种问题,刚开始idea不一定work需要不断调整,另外很多东西没有现成的轮子需要自己实现。因此理论能力和工程能力都要具备。后者一般.

喜欢打酱油的老鸟 6177

基于TensorFlow的卷积神经网络图像分类实战项目

给定一张输入图像 $ I \in \mathbb{R}^{H \times W \times C} $,目标是预测其所属类别标签 $ y \in {1,2,…,K} $,其中 $ K $ 是类别总数。听起来很简单,但实际落地时要考虑的问题可不少。比如:- 数据是否均衡?有些类样本太少怎么办?- 模型会不会过拟合?训练集表现好,测试就崩盘?- 推理速度够不够快?能不能满足实时性要求?这些问题都需要我们在建模之初就想清楚。最后一步,也是最难的一步:上线服务。

weixin_33670640的博客 1057

常见消息队列:ActiveMQ、RabbitMQ、RocketMQ、Kafka的区别总结

常见消息队列:ActiveMQ、RabbitMQ、RocketMQ、Kafka的区别。

qq_30713721的博客 3472

CV +工业质检 全景梳理

一句话总结:工业质检 CV 已从“能检”走向“会思考”,精度 99 % 以上、上线周期 <1 周、单工位 ROI <12 个月,成为智能制造标配。一句话总结:先用自监督学“正常”→用 GAN 造“缺陷”→用 Few-shot 微调,三步闭环把缺陷检测从“需要上万样本”降到“几十张即可投产”。损失 = InfoNCE + MSE。把仓库克隆下来,按上面 4 步即可在 1 小时内完成“工业缺陷小样本 → 高质量生成”的完整闭环。目的:用无缺陷正常样本学到高判别性特征,解决“缺陷样本极少”问题。

matlab_python22的博客 1199

双层优化入门(1)—基本原理与求解方法(附matlab代码)

双层优化问题(Bilevel Programming Problems),也被称为双层规划,最早由Stackelberg与1934年在经济学相关研究中提出,因此也被称为Stackelberg问题。双层规划问题一般具有层次性、独立性、冲突性、优先性和自主性等特点。 本文介绍了双层优化的原理与求解方法,并提供了相应的matlab代码供参考学习。

配电网和matlab的博客 5万+

CV 加持的工业检测,从算法选型到模型部署

随着AI技术的逐渐成熟,越来越多的传统企业希望借助AI技术,实现智能质检。传统质检更多依靠有经验的人工劳动力来实现质量检查甚至分拣,但这样的方式,许多场景会有检查效率低,审核质量不稳定,...

我爱计算机视觉 3111

Zephyr RTOS线程间通讯实战:消息队列与信号量的5个经典应用场景

本文深入探讨了Zephyr RTOS中线程间通讯的核心机制,通过消息队列与信号量的组合,详细解析了传感器数据处理流水线、命令分发系统、日志记录、资源池管理以及周期性任务协同调度等5个经典应用场景。这些实战模式为构建高效、稳定且可维护的嵌入式多线程系统提供了清晰的设计蓝图和代码范例。

tomato的博客 96

Adobe软件非正版弹窗终极解决方案:PS/Ai/PR/AE禁用提示一键清除指南

本文提供了Adobe软件非正版弹窗的终极解决方案,包括PS、Ai、PR、AE等软件的禁用提示一键清除指南。通过专业清理工具和手动修改系统文件的方法,帮助用户彻底解决Adobe弹窗问题,提升使用体验。适用于Windows和Mac系统用户,操作简单高效。

weixin_30872337的博客 5433

工业视觉常见图像类型

工业视觉常见图像类型 一 C#的Bitmap

Major_S的博客 711

CentOS7全隔离环境部署Nvidia Tesla A100 40G驱动与CUDA工具包实战

本文详细介绍了在CentOS7全隔离环境下部署Nvidia Tesla A100 40G显卡驱动与CUDA工具包的实战经验。通过构建本地YUM仓库、解决驱动依赖问题以及三重验证法,确保Tesla A100与CUDA 11.7的完美兼容。同时分享了Anaconda3离线部署技巧,帮助用户在隔离环境中高效搭建Python GPU开发环境。

weixin_27215337的博客 403

【CV项目实战】纯新手如何从零开始完成一个工业级图像分割任务的整个流程?...

大家好,欢迎来到专栏《CV项目实战》,在这个专栏中我们会讲述计算机视觉相关的项目实战,有大型的完整项目,也有精炼的核心算法实战。本次主要讲述一个完整的图...

hacker_long的专栏 2020

uds协议之can总线程序升级

uds协议简介 如何利用uds协议升级 升级流程 uds服务类型介绍

fangye945a的博客 1万+

ADS负载牵引仿真避坑指南:从零开始搞定Loadpull模板设置

本文是一份针对ADS负载牵引仿真的深度避坑指南,详细解析了Loadpull模板从初始化配置到结果解读的全过程。文章重点阐述了如何根据设计目标灵活设置扫描圆、采样点等关键参数,以解决仿真不收敛、结果不完整等常见问题,并指导读者在功率与效率之间做出理性权衡,最终提取出用于匹配网络设计的最优负载阻抗。

weixin_29234423的博客 598

【CV实战】年轻人的第一个深度学习CV项目应该是什么样的?(支持13大深度学习开源框架)...

计算机视觉发展至今,许多技术已经非常成熟了,在各行各业落地业务非常多,因此不断的有新同学入行。本次我们就来介绍,对于新手来说,如何做一个最合适的项目。本次讲述一个完整的工业级别图像分类项目...

hacker_long的专栏 1295

基于YOLOv26的智能农业无人机巡检系统

本文提出了一种基于YOLOv26的智能农业无人机巡检系统。系统采用模块化设计,包含数据采集、预处理、目标检测、图像拼接、分析决策和部署控制六大核心模块。无人机搭载多光谱相机、RGB相机和热红外相机进行农田数据采集,通过YOLOv26实现作物病虫害、杂草及灌溉设备的检测。系统采用PyTorch、OpenCV等技术栈,支持无人机自动路径规划和多传感器数据采集,并包含数据增强和预处理功能。该系统可有效提升农业巡检效率,为精准农业提供智能化解决方案。

一键难忘的博客 810

零基础学 AI:方向 1 计算机视觉(CV)核心任务与实战指南

本文为零基础学习者提供计算机视觉(CV)入门指南。首先介绍CV五大核心任务:图像分类、目标检测、图像分割、人脸识别和GAN图像生成,分析各任务的技术原理、主流算法和典型应用场景。其次推荐常用工具库,包括OpenCV、PaddlePaddle、MMDetection等,并指导如何选择适合的工具。最后通过两个实战案例(YOLOv8实时目标检测和U-Net医学影像分割)详细演示项目实现流程。文章还为零基础学习者规划了1-6个月的学习路径,建议从实战入手,逐步深入,并关注工业界最新趋势。全文旨在帮助读者快速掌握CV

YongCheng_Liang的博客 2261

SAP B1中文培訓教材_SDK

公司内部培训资料,对开发工具 ,DIAPI, UIAPI,Add-ON有详细解说

pscad IEEE标准系统搭建说明

pscad IEEE标准系统搭建说明,包含典型的IEEE 9节点、14节点、30节点、39节点和118节点系统。内含标准测试系统的原始数据和潮流结果。

简历上写CV开源项目,有用吗?

常有粉丝留言问我:简历上写CV的开源项目有用吗?答案很简单,没什么用。以“实时目标检测”这一自动驾驶、机器人等行业的重要任务来举例,尽管你可以获得一些开源代码,但几乎没有任何开源代码能在诸...

Paper weekly 990

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

PyTorch图像分类实战:从ResNet到ViT,工业级目标检测系统开发全流程

工业数据通常按“类别分文件夹”或“标注文件+图片”存储,需自定义Datasetimport osimport cv2"""工业数据集加载器(按类别分文件夹存储)data_dir结构:data_dir/class0/img1.jpgimg2.jpgclass1/img3.jpg..."""# 读取类别和样本路径"""加载所有样本路径和标签"""# 读取图片(Albumentations需要RGB格式)# 应用增强# 实例化数据集# 加载数据。

2501_91798322的博客 1261

STM32F407中文手册

STM32f407中文手册 本参考手册面向应用开发人员,提供有关使用 STM32F405xx/07xx、STM32F415xx/17xx、 STM32F42xxx 和 STM32F43xxx 微控制器存储器与外设的完整信息。STM32F405xx/07xx、STM32F415xx/17xx、STM32F42xxx 和 STM32F43xxx 构成一个微 控制器系列,各产品具有不同的存储器大小、封装和外设。有关订购信息以及器件的机械与电气特性,请参见数据手册。有关 ARM Cortex™-M4F 内核的信息,请参见《Cortex™-M4F 技术参考手册》。

如何跟随有三从零进阶中级CV算法工程师

再过一段时间可能就要迈出成为真正的创业者的关键一步了,有三AI夏季划再招一些学生作为人才储备,同时也是带一些人真正系统性成为深度学习CV算法工程师。文/编辑 | 言有三...

hacker_long的专栏 817

PyTorch单标签图像分类:从能跑到可部署的CV工程实践

图像分类是计算机视觉最基础且核心的任务类型,而单标签分类作为其典型范式,构成了模型设计、数据流水线与训练优化的最小可验证单元。它揭示了深度学习框架底层原理——从Tensor维度管理、DataLoader多进程IO调度,到CrossEntropyLoss的数值稳定性机制和torch.compile加速逻辑。掌握该任务,意味着具备构建鲁棒视觉系统的工程能力:支持工业级部署(JIT导出)、边缘设备适配(轻量模型选型)、数据增强自动化(AutoAugment)及性能瓶颈定位(torch.profiler)。本文聚焦

weixin_29035405的博客 261

mmclassification工业级图像分类训练实战:从数据到收敛的12个关键决策点

图像分类是计算机视觉最基础且广泛应用的任务,其核心在于构建稳定、可复现、高性能的端到端训练流程。理解数据组织规范、增强策略的物理意义、学习率调度的数学原理,以及分布式训练中的梯度同步机制,是突破精度瓶颈与工程落地的关键。mmclassification作为OpenMMLab推出的工业级框架,通过模块化设计、配置驱动架构和四层抽象,将多年CV工程经验沉淀为开箱即用的训练流水线。它不仅支持ResNet、ViT、ConvNeXt等主流模型,更深度集成混合精度训练、分层权重衰减、自动日志与断点续训等生产就绪能力,显

weixin_28475533的博客 294

计算机视觉核心任务与实战:从图像分类到目标检测

计算机视觉(CV)是人工智能的重要分支,通过算法让计算机理解图像和视频内容。其核心技术包括图像分类、目标检测、图像分割和图像生成四大任务,分别解决'是什么''在哪里''像素归属'和'创造图像'的问题。在工程实践中,PyTorch和YOLOv5等工具极大降低了CV应用门槛。以目标检测为例,YOLO系列算法在保持高精度的同时实现了实时处理,配合TensorRT加速和模型量化技术,可广泛应用于安防、医疗和自动驾驶等领域。掌握迁移学习和模型优化技巧,如知识蒸馏和剪枝量化,能显著提升工业场景下的部署效率。

weixin_32501329的博客 294
上一篇: 【杂谈】有三AI知识星球一周年了!为什么公众号+星球才是完整的?
下一篇: 【CV项目实战】纯新手如何从零开始完成一个工业级图像分割任务的整个流程?...
言有三
言有三 领域专家: 人工智能技术领域 领域专家: 人工智能技术领域
博客等级 码龄15年 3635粉丝 976原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

言有三

三人行必有AI

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值