机器学习(8) 您所在的位置:网站首页 人脸定位是什么意思呀 机器学习(8)

机器学习(8)

2024-07-12 05:51| 来源: 网络整理| 查看: 265

目的与过程概要

1.目的:输入一张图片,让机器在人脸的位置画出一个框 这里写图片描述

2.过程概要

训练一个能识别一张227*227的图像是否是人脸的二分类模型(使用AlexNet网络) 这里写图片描述=>人脸 这里写图片描述=>非人脸修改训练好的网络模型,数据层改为输入层,全链接层改为全卷积层(起到窗口滑动的作用)将输入的图片进行放大缩小变换scal变换根据图像的大小,动态的修改网络模型的数据层 环境 首先,要安装以下环境 Ubuntu:pythonanaconda:机器学习的python环境,包含了许多必要的库,比如numpyopencv:机器视觉常用库caffe :网络训练的基础cuda:如果用Gpu 运行,需要安装的包 第一步:数据准备 1.标记好的数据

一般这些数据网上都有(http://blog.csdn.net/chenriwei2/article/details/50631212),不用我们自己制作。如果得到的是原始的数据(一张完整的图,指定人脸的区域),那么就需要进行样本采样

裁剪工具:http://www.jianshu.com/p/856d1d420854,或者使用opencv裁剪 2.正负样本采样 - 正样本采样:即人脸的部分 ,需要把图片中人脸的部分裁剪出来,要注意的是,裁剪出来后的图片要人工过一遍,数据的好坏对训练的结果影响很大。 - 负样本采样:在非人脸的部分进行随机的采样 负样本的采样比较复杂,先随机在图片上取图,然后计算与人脸部分的iOU,即重叠率,设定一个阈值,小于这个阈值的就认为是非人脸 - IOU: http://blog.csdn.net/eddy_zheng/article/details/52126641 3.制作lmdb数据

lmdb是caffe的训练数据格式,制作lmdb数据需要准备图片数据和标签数据 - 图片数据是我们上面裁剪好和做好分类的图片 - 标签数据是txt文件,格式是 图片路径+空格+标签 如:1/23039_nonface_0image30595.jpg 1 - 把数据集切分成训练集(train)和测试集合(val) - 使用以下代码进行lmdb数据的生成

#!/usr/bin/env sh EXAMPLE=~/code/learn # 输出的文件夹根目录 DATA=~/code/learn #存放标签数据的根目录,该文件夹下有对应的标签数据 TOOLS=~/code/caffe/build/tools # caffe安装目录的tools文件夹 TRAIN_DATA_ROOT=~/code/learn/train/train/ # 存放训练数据集的目录 VAL_DATA_ROOT=~/code/learn/train/val/ # 存放测试数据集的目录 #resize图片的大小为227*227 RESIZE=true if $RESIZE; then RESIZE_HEIGHT=227 RESIZE_WIDTH=227 else RESIZE_HEIGHT=0 RESIZE_WIDTH=0 fi if [ ! -d "$TRAIN_DATA_ROOT" ]; then echo "Error: TRAIN_DATA_ROOT is not a path to a directory: $TRAIN_DATA_ROOT" echo "Set the TRAIN_DATA_ROOT variable in create_face_48.sh to the path" \ "where the face_48 training data is stored." exit 1 fi if [ ! -d "$VAL_DATA_ROOT" ]; then echo "Error: VAL_DATA_ROOT is not a path to a directory: $VAL_DATA_ROOT" echo "Set the VAL_DATA_ROOT variable in create_face_48.sh to the path" \ "where the face_48 validation data is stored." exit 1 fi echo "Creating train lmdb..." # 生成训练集lmdb,生成结果在 $EXAMPLE/face_train_lmdb GLOG_logtostderr=1 $TOOLS/convert_imageset \ --resize_height=$RESIZE_HEIGHT \ --resize_width=$RESIZE_WIDTH \ --shuffle \ $TRAIN_DATA_ROOT \ $DATA/train.txt \ $EXAMPLE/face_train_lmdb echo "Creating val lmdb..." # 生成测试集lmdb,生成结果在 $EXAMPLE/face_val_lmdb GLOG_logtostderr=1 $TOOLS/convert_imageset \ --resize_height=$RESIZE_HEIGHT \ --resize_width=$RESIZE_WIDTH \ --shuffle \ $VAL_DATA_ROOT \ $DATA/val.txt \ $EXAMPLE/face_val_lmdb echo "Done." Status API Training Shop Blog About 4.结果

经过第一步,你应该获取的最终结果是 1.训练集合的lmdb文件:face_train_lmdb文件夹对应的data.mdb和lock.mdb 2.测试集合的lmdb文件:face_val_lmdb文件夹对应的data.mdb和lock.mdb

第二步:训练一个识别图片是否人脸的神经网络

在准备好了数据之后,第二步是训练一个能够识别一张227*227的图片是否是人脸的神经网络

1.网络模型配置

在这里我们不准备讲解这些具体的神经网络,假如你不知道什么是卷积,relu,池化,全连接层的话,你直接使用这些网络配置文件就好了,我们在这里使用的是AlexNet网络(AlexNet:参考http://blog.csdn.net/chaipp0607/article/details/72847422)

caffe 网络配置 :train.prototxt train.prototxt文件是定义网络模型的文件, 需要修改的是lmdb数据的路径,对应的训练集和测试集的lmdb数据,以及减均值的路径 ############################ 注意:这里只是train.prototxt文件的一部分,你需要下载完整的train.prototxt ############################# layer { top: "data" top: "label" name: "data" type: "Data" data_param { source: "/home/tas/code/learn/face_train_lmdb" #训练集的lmdb路径 backend:LMDB batch_size: 64 } transform_param { #mean_file: "/home/tas/code/caffe/data/ilsvrc12/imagenet_mean.binaryproto" # caffe安装目录对应的文件,用于减均值计算 mirror: true } include: { phase: TRAIN } } 运行配置:solver.prototxt 参考:https://www.cnblogs.com/denny402/p/5074049.html net: "/home/tas/code/learn/train.prototxt" # 定义的网络模型 test_iter: 100 # 测试时迭代的次数,batch_size(在train.prototxt定义)*test_iter要等于测试集合的大小 test_interval: 500 # 每训练500次进行一次测试 # lr for fine-tuning should be lower than when starting from scratch base_lr: 0.001 # 基础学习率 lr_policy: "step" gamma: 0.1 # stepsize should also be lower, as we're closer to being done stepsize: 20000 display: 100 max_iter: 100000 # 训练的次数 momentum: 0.9 weight_decay: 0.0005 snapshot: 10000 # 每训练10000次保存一次模型 snapshot_prefix: "/home/tas/code/learn/model/" # 最后生成模型的保存路径 # uncomment the following to default to CPU mode solving solver_mode: GPU # 这里使用GPU的话需要安装CUDA等环境,并且caffe编译时要注释掉CPU_only,否则使用CPU 运行文件:train.sh #!/usr/bin/env sh /home/tas/code/caffe/build/tools/caffe train --solver=/home/tas/code/learn/solver.prototxt \ #--snapshot=/home/tas/code/learn/model/_iter_72484.solverstate \ # 如果要接着上次的训练结果据需运行,取消注释这行,并制定到对应上次训练后生成的文件 #--gpu all # GPU模式取消注释这行 执行训练,打开终端,进入到train.sh的目录,在命令行里敲入以下代码就开始训练了 sh train.sh 2.防止过拟合

在我们训练的过程中,可能出现过拟合的情况,过拟合的情况就是在训练集里的效果很好,准确率很高,但是在测试集的测试的结果却很差,我们可以挑选效果最好的model,调低基础学习率,再次训练

3.GPU运行 - 安装CUDA - caffe 中Makefile.config 注释 CPU_only,重新编译 - 设置GPU模式:solver.prototxt - train.sh选用GPU

###4.结果 经过第二步,你得到的结果应该是一个.caffemodel文件

第三步,编写代码

###1.修改模型

在写代码前,我们需要先调整下网络模型train.prototxt,修改后的文件为deploy_full_conv.prototxt,调整的目的 删除数据层,修改为输入层 由于我们现在没有数据的,每次输入一张图片输入模型进行运算,需要先删除掉data层,改为如下的代码 name: "CaffeNet_full_conv" input: "data" input_dim: 1 # 每次输入一张图片 input_dim: 3 # 图片的RPG三通道 input_dim: 500 # 图片的宽 input_dim: 500 # 图片的高 把全链接层改为全卷积层达到窗口滑动的效果。 训练好的模型只能识别227*227大小的图片,我们需要把全连接层改为全卷积层,这样子能够达到一个窗口滑动的效果,扫描整张图片。所以就会的输出结果应该是多个结果的概率矩阵。 修改全连接层只需要把对应的layer层的type从InnerProduct 修改为 Convolution,并且修改全连接的参数inner_product_param为卷积的参数convolution_param,具体的参数是一样的,只需要再增加一个卷积核大小的参数kernel_size 修改前的第六层 layer { name: "fc6" type: "InnerProduct" bottom: "pool5" top: "fc6" param { lr_mult: 1 decay_mult: 1 } param { lr_mult: 2 decay_mult: 0 } inner_product_param { num_output: 4096 weight_filler { type: "gaussian" std: 0.005 } bias_filler { type: "constant" value: 0.1 } } }

修改后

layer { name: "fc6-conv" type: "Convolution" bottom: "pool5" top: "fc6-conv" param { lr_mult: 1 decay_mult: 1 } param { lr_mult: 2 decay_mult: 0 } convolution_param { num_output: 4096 kernel_size: 6 weight_filler { type: "gaussian" std: 0.005 } bias_filler { type: "constant" value: 1 } } }

同样对其他两层全连接层做一样的操作

删除两层pool层,增加计算精度删除accuracy层和loss层,因为我们已经不需要计算精度了,我们只需要一个结果增加Softmax层,将计算结果转化为概率输出 2.图片的scal变换

上面训练的模型只能识别一个227227大小的,但是输入的图片内人脸的大小不一定是这么大,有可能偏大500500,或者偏小5050,所以需要对原图多次进行缩放后才作为结果输入,这样子总有一张图的头像区域的大小是接近227227的。

###3. 动态修改模型 由于每张输入的图片大小都可能不一样,需要动态的修改输入层图片的大小 ###4.非最大值抑制(NMS) 一个人脸可能被多次识别,但是我们只需要一个最准确的结果就可以了, 这里写图片描述 取概率最大值后 这里写图片描述 具体可参考:http://blog.csdn.net/shuzfan/article/details/52711706 或者直接使用以下的代码:并最终调用nms_average(boxes_nums, 1, 0.2) boxes_nums 是模型数据的结果,

class Point(object): def __init__(self, x, y): self.x = x self.y = y def calculateDistance(x1,y1,x2,y2): dist = math.sqrt((x2 - x1)**2 + (y2 - y1)**2) return dist def range_overlap(a_min, a_max, b_min, b_max): return (a_min 0.2: rects.append([boxes[i,0], boxes[i,1], boxes[i,2]-boxes[i,0], boxes[i,3]-boxes[i,1]]) rects, weights = cv2.groupRectangles(rects, groupThresh, overlapThresh) rectangles = [] for i in range(len(rects)): testRect = Rect( Point(rects[i,0], rects[i,1]), Point(rects[i,0]+rects[i,2], rects[i,1]+rects[i,3])) rectangles.append(testRect) clusters = [] for rect in rectangles: matched = 0 for cluster in clusters: if (rect_merge( rect, cluster , 0.2) ): matched=1 cluster.left = (cluster.left + rect.left )/2 cluster.right = ( cluster.right+ rect.right )/2 cluster.top = ( cluster.top+ rect.top )/2 cluster.bottom = ( cluster.bottom+ rect.bottom )/2 if ( not matched ): clusters.append( rect ) result_boxes = [] for i in range(len(clusters)): result_boxes.append([clusters[i].left, clusters[i].bottom, clusters[i].right, clusters[i].top, 1]) return result_boxes 人脸坐标映射

由于最终结果是一个概率点,我们需要根据网络模型结构把它映射回原图

def GenrateBoundingBox(featureMap, scale): boundingBox = [] stride = 32 # 可以把网络结构进行了32倍卷积 cellSize = 227 #滑动窗口的大小 for (x, y), prob in np.ndenumerate(featureMap): if prob>0.95: boundingBox.append([float(stride*y)/scale, float(stride*x)/scale, float(stride * y+ cellSize - 1) / scale, float(stride*x+ cellSize - 1)/scale, prob]) return boundingBox 完整的代码 注意:这里的"/home/tas/code/"是我本机的路径,根据你自己的路径进行修改 # -*- coding: utf-8 -*- import sys import os from math import pow from PIL import Image, ImageDraw,ImageFont import cv2 import math import random import numpy as np caffe_root = '/home/tas/code/caffe/' sys.path.insert(0, caffe_root+'python') # 设置log等级 os.environ['GLOG_minloglevel'] = '2' import caffe caffe.set_mode_gpu() temp_path = '/home/tas/code/learn/temp_img/' def face_detection(imgFile): # 这里调用的是第二步生成的模型和第三步修改后的神经网络 net_full_conv = caffe.Net('/home/tas/code/learn/deploy_full_conv.prototxt', '/home/tas/code/learn/alexnet_iter_50000_full_conv.caffemodel', caffe.TEST) scales = [] # 刻度 factor = 0.79 # 变换的倍数 img = cv2.imread(imgFile) # 最大倍数 largest = min(2, 4000/max(img.shape[0:2])) # 最小的边的长度 minD = largest*min(img.shape[0:2]) scale = largest # 从最大到最小227,获取变换的倍数 while minD >= 227: scales.append(scale) scale *= factor minD *= factor # 存储人脸图 total_box = [] # 变换图片 for scale in scales: fileName = "img_"+str(scale)+'.jpg' scale_img = cv2.resize(img, (int((img.shape[0]*scale)), int(img.shape[1]*scale))) cv2.imwrite(temp_path+fileName, scale_img) im = caffe.io.load_image(temp_path+fileName) # 动态修改数据层的大小?这里为什么时1,0 而不是0,1 net_full_conv.blobs['data'].reshape(1, 3, scale_img.shape[1], scale_img.shape[0]) transformer = caffe.io.Transformer({'data':net_full_conv.blobs['data'].data.shape}) # 减均值,归一化 transformer.set_mean('data', np.load(caffe_root+'python/caffe/imagenet/ilsvrc_2012_mean.npy')) # 维度变换 ,cafee默认的时BGR格式,要把RGB(0,1,2)改为BGR(2,0,1) transformer.set_transpose('data', (2, 0, 1)) # 像素 transformer.set_raw_scale('data', 255) transformer.set_channel_swap('data', (2, 1, 0)) # 人脸坐标映射 # 前先传播,映射到原始图像的位置 out = net_full_conv.forward_all(data=np.asarray(transformer.preprocess('data', im))) #out['prob'][0, 1] 0表示类别,1表示概率 boxes = GenrateBoundingBox(out['prob'][0, 1], scale) if(boxes): total_box.extend(boxes) boxes_nums = np.array(total_box) #nms 处理 true_boxes = nms_average(boxes_nums, 1, 0.2) if not true_boxes == []: x1,y1,x2,y2 = true_boxes[0][:-1] cv2.rectangle(img,(int(x1), int(y1)), (int(x2), int(y2)), (0, 0, 255), thickness=5) cv2.imwrite('/home/tas/code/learn/result_img/result.jpg', img) # cv2.imshow('test', img) def GenrateBoundingBox(featureMap, scale): boundingBox = [] stride = 32 cellSize = 227 #滑动窗口的大小 for (x, y), prob in np.ndenumerate(featureMap): if prob>0.95: boundingBox.append([float(stride*y)/scale, float(stride*x)/scale, float(stride * y+ cellSize - 1) / scale, float(stride*x+ cellSize - 1)/scale, prob]) return boundingBox class Point(object): def __init__(self, x, y): self.x = x self.y = y def calculateDistance(x1,y1,x2,y2): dist = math.sqrt((x2 - x1)**2 + (y2 - y1)**2) return dist def range_overlap(a_min, a_max, b_min, b_max): return (a_min 0.2: rects.append([boxes[i,0], boxes[i,1], boxes[i,2]-boxes[i,0], boxes[i,3]-boxes[i,1]]) rects, weights = cv2.groupRectangles(rects, groupThresh, overlapThresh) rectangles = [] for i in range(len(rects)): testRect = Rect( Point(rects[i,0], rects[i,1]), Point(rects[i,0]+rects[i,2], rects[i,1]+rects[i,3])) rectangles.append(testRect) clusters = [] for rect in rectangles: matched = 0 for cluster in clusters: if (rect_merge( rect, cluster , 0.2) ): matched=1 cluster.left = (cluster.left + rect.left )/2 cluster.right = ( cluster.right+ rect.right )/2 cluster.top = ( cluster.top+ rect.top )/2 cluster.bottom = ( cluster.bottom+ rect.bottom )/2 if ( not matched ): clusters.append( rect ) result_boxes = [] for i in range(len(clusters)): result_boxes.append([clusters[i].left, clusters[i].bottom, clusters[i].right, clusters[i].top, 1]) return result_boxes face_detection('/home/tas/code/learn/result_img/timg.jpeg') 测试

最后,调用函数,就会生成一张倍圈中人脸的图片

face_detection('test.jpg') 问题

1.在哪里进行窗口滑动:将全链接层改为全卷积层 2.为什么要用全卷积层替换全链接层 参考:http://blog.csdn.net/nnnnnnnnnnnny/article/details/70194432

文件和数据下载

链接: https://pan.baidu.com/s/1kUE2B7D 密码: dmxe 数据缺失请留言



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有