一、人脸验证VS人脸识别 验证:一对一关系匹配识别:数据库中存在多个数据,进行一对多的匹配人脸验证的准确率很高,不代表人脸识别准确率很高,错误率会被放大二、oneshot学习 有一种考虑方式是仅仅训练几个员工的图片,但是会导致一个问题:每加入一个新员工,就需要重新训练整个网络,计算量太大。所以引入oneshot学习,简单来说,就是反馈需要识别的图像和数据库图像的差距。 相似性函数similarityfunctiond(img1,img2)differences(img1,img2)d(img1,img2)differences{(img1,img2)}d(img1,img2)differences(img1,img2)verification:verification:verification:d,samedleqau,samed,samed,differentdau,differentd,different 将输入图片和数据库图片一一验证,计算出每个差距值d,差距最小的就是匹配的图片。三、siamese网络架构 前面介绍了利用相似度一次性判断图片匹配与否,那么具体是用什么数值进行相似度计算呢?在DeepFace论文中提出了siamese网络,现在来看看原理。在下图中,给出两个卷积神经网络结果,可以对任意两幅图进行比对。两幅图经过相同参数的卷积神经网络后,都会在全连接层输出一个向量。如果我们认为该网络输出的编码结果可以代表原图,那么计算这两次输出向量的差的范式,即可计算两幅图的差距。这便是DeepFace中siamese的核心思想。 其中向量差的范式即d(x(i),x(j))f(x(i))f(x(j))22d(x{(i)},x{(j)})f(x{(i)})f(x{(j)})22d(x(i),x(j))f(x(i))f(x(j))22 网络学习目标 四、Triplet损失 如果想通过训练网络参数得到优质的人脸图片编码,较好的一个方法就是三元组损失函数然后梯度下降法。三元组损失的意思就是:每次都会看三张图片,一张Anchor(A),一张Positive(P),一张Negative(N) 我们希望得到的目标是 移项后得到 考虑到做差为零的情况是无效的,所以加入一个超参数alpha,使得两个范式的差小于0有效。 也称这个alpha是间隔。至于这个间隔应该怎么理解,接着往下看。 我们要得到的目标是上面这个不等式对吧,如果d(A,P)0。50,d(A,N)0。51d(A,P)0。50,d(A,N)0。51d(A,P)0。50,d(A,N)0。51,虽然满足不等式条件,但是,这个间距非常小,即0。01alpha0。010。01很小,所以不利于很好的训练,所以要想拉大这个间距有利于训练,就需要设置更大的alphaalphaalpha,比如等于0。2。那么此时d(A,P)0。50,0。2d(A,N)0。70。d(A,P)0。50,alpha0。2Rightarrowd(A,N)0。70。d(A,P)0。50,0。2d(A,N)0。70。 基于上述不等式,来定义三元组损失函数L(A,P,N)max(f(A)f(P)2f(A)f(N)2,0)L(A,P,N)max(f(A)f(P)2f(A)f(N)2,0)L(A,P,N)max(f(A)f(P)2f(A)f(N)2,0) 分析一下这个式子,如果f(A)f(P)2f(A)f(N)20f(A)f(P)2f(A)f(N)20f(A)f(P)2f(A)f(N)20,则损失值为0,而网络不会关心负值是多少;如果f(A)f(P)2f(A)f(N)20f(A)f(P)2f(A)f(N)20f(A)f(P)2f(A)f(N)20,则损失值取这个正数。整个网络的代价函数就可以定义了:Ji1mL(A(i),P(i),N(i))Jsum{i1}mL(A{(i)},P{(i)},N{(i)})Ji1mL(A(i),P(i),N(i)) 写到这里我们发现数据集中需要构造成对的三元组(A,P,N),假设10k张人脸图片中包含了1k个人,那么平均每个人有10张照片。如果某个人只有一张自己的人脸图片,那么系统无法训练。将训练好的系统应用到人脸识别中,就可以一次性识别某个人的人脸了(此时不需要给多张此人的人脸进去)。 数据集怎么样构造?遵守一个原则:AP是同一个人,AN是不同的人。但是由于N的随机性,AN很大概率差距很大,认为网络训练好像不能学习到很有用的东西。所以选择难训练的三元组学习。d(A,P)d(A,N)d(A,P)approxd(A,N)d(A,P)d(A,N) 通过三元组损失函数进行梯度下降,可以优化网络参数,得到最佳的人脸图片编码。五、面部验证和二分类 三元组损失确实是学习训练人脸识别卷积神经网络参数的好方法,现在看看另一种方法:将人脸识别转化为二分类问题。 将输出的两个向量作为逻辑回归单元的输入,最后输出预测值ywidehatyy,如果人脸匹配则为1,不匹配则为0。(在之前做三元组损失的时候,我就在思考这个问题,能不能不计算三幅图之间的差距,那样好像显得过程很繁琐,能不能用二分类的方式直接得到01分类)注意:上下两套卷积网络(比如用Siamese网络)的参数是共享的、绑定的。 ysigmoid(k1128wif(x(i))kf(x(j))kb)widehatysigmoid(sum{k1}{128}wif(x{(i)})kf(x{(j)})kb)ysigmoid(k1128wif(x(i))kf(x(j))kb) 可以将f(x(i))kf(x(j))kf(x{(i)})kf(x{(j)})kf(x(i))kf(x(j))k替换为〔f(x(i))kf(x(j))k〕2f(x(i))kf(x(j))kfrac{〔f(x{(i)})kf(x{(j)})k〕2}{f(x{(i)})kf(x{(j)})k}f(x(i))kf(x(j))k〔f(x(i))kf(x(j))k〕2 这一块称作卡方平方相似度卡方平方公式。 假如上面这幅图是需要验证的图片,下面那张图片是数据库中的图片。可以事先计算好数据库中的图片编码,当有需要验证的图片到来时,只需要计算上面网络的正向传播编码结果,然后输入逻辑回归,输出得到预测值01。这就是预先计算方式来节省大量计算时间。 最后总结一下监督学习中的人脸二分类问题输入的两张图片,通过Siamese卷积神经网络后得到人脸编码,通过逻辑回归得到预测值,其中训练过程是反向传播优化网络和逻辑回归中的参数。 软硬件环境ubuntu18。0464bitGTX1070Tianacondawithpython3。6facerecognition1。2。3dlibopencvfacerecognition简介 facerecognition号称是世界上最简单的基于python的人脸识别库,是在大名鼎鼎的深度学习框架dlib上做的整合,dlib模型在LFW(LabeledFacesintheWild)能有99。38的准确率。另外facerecognition提供了相应的命令行工具,可以通过命令行来对图片文件夹进行人脸识别,非常的酷。安装facerecognition 可以利用pip来安装pipinstallfacerecognition复制代码 或者从源码开始安装gitclonehttps:github。comageitgeyfacerecognition。gitcdfacerecognitionpythonsetup。pyinstall复制代码facerecognition工作流找出人脸 第一步就是要找出给定图片中包含的全部的脸的位置 importfacerecognitionimagefacerecognition。loadimagefile(yourfile。jpg)facelocationsfacerecognition。facelocations(image)复制代码操作特征点 找出每个人的眼睛、鼻子、嘴巴和下巴 importfacerecognitionimagefacerecognition。loadimagefile(yourfile。jpg)facelocationsfacerecognition。facelocations(image)复制代码进行识别 最后一步,也是最关键的一步,识别出这张脸是属于谁的。facerecognition使用了欧几里得距离(可以参考我的另一篇文章xugaoxiang。com20191130)来确定是不是同一张脸。 importfacerecognitionknownimagefacerecognition。loadimagefile(biden。jpg)unknownimagefacerecognition。loadimagefile(unknown。jpg)bidenencodingfacerecognition。faceencodings(knownimage)〔0〕unknownencodingfacerecognition。faceencodings(unknownimage)〔0〕resultsfacerecognition。comparefaces(〔bidenencoding〕,unknownencoding)复制代码命令行工具 facerecognition提供了2个命令行工具facedetection找出给定图片或图片文件夹中的人脸facerecognition进行人脸识别 具体如何使用,可以通过help来查看参数。facedetection比较简单,这里就不讲了。命令行工具facerecognition使用 要进行人脸识别,首先必须准备已知人员的信息,这里用一个独立的文件夹来存放,比如known,然后每一个人的图片都以他的名字来命令,如JoeBiden。jpg、Kobe。jpg。 然后将需要识别的图片放置在另一个文件夹中,如unknown,执行以下命令进行识别facerecognitionknownunknown复制代码 默认情况下,不设置阈值的话,正确识别率是非常非常低的。在实际情况下,需要根据自己的情况,来适当调整阈值。在同样的测试环境下,将相似度阈设成了0。38,识别结果就正确了。facerecognitionknownunknowntolerance0。38复制代码 打印中的unknownperson表明是陌生人 另一个有用的参数是cpus,如果你使用的是多核CPU,可以利用这个参数来提升识别速度。cpus1表示使用所有的CPU核。KNN分类器 KNN(KNearestNeighbor)通过测量不同特征值之间的距离进行分类。它的思路是:如果一个样本在特征空间中的k个最相似(即特征空间中最邻近)的样本中的大多数属于某一个类别,则该样本也属于这个类别,其中K通常是不大于20的整数。KNN算法中,所选择的邻居都是已经正确分类的对象。该方法在定类决策上只依据最邻近的一个或者几个样本的类别来决定待分样本所属的类别。 在实际的项目中,一般都会用一个分类器(classifier)来存储已知人脸的数据,方便管理。facerecognition使用了基于KNN算法的分类器。制作分类器 代码来自examplesfacerecognitionknn。py,注释很详细了,就不一一解释了。经测试,同一人的图片越多,识别的准确率就越高。deftrain(traindir,modelsavepathNone,nneighborsNone,knnalgoballtree,verboseFalse):Trainsaknearestneighborsclassifierforfacerecognition。:paramtraindir:directorythatcontainsasubdirectoryforeachknownperson,withitsname。(Viewinsourcecodetoseetraindirexampletreestructure)Structure:traindirperson1somename1。jpegsomename2。jpeg。。。person2somename1。jpegsomename2。jpeg。。。:parammodelsavepath:(optional)pathtosavemodelondisk:paramnneighbors:(optional)numberofneighborstoweighinclassification。Chosenautomaticallyifnotspecified:paramknnalgo:(optional)underlyingdatastructuretosupportknn。defaultisballtree:paramverbose:verbosityoftraining:return:returnsknnclassifierthatwastrainedonthegivendata。X〔〕y〔〕Loopthrougheachpersoninthetrainingsetforclassdirinos。listdir(traindir):ifnotos。path。isdir(os。path。join(traindir,classdir)):continueLoopthrougheachtrainingimageforthecurrentpersonforimgpathinimagefilesinfolder(os。path。join(traindir,classdir)):imagefacerecognition。loadimagefile(imgpath)faceboundingboxesfacerecognition。facelocations(image)iflen(faceboundingboxes)!1:Iftherearenopeople(ortoomanypeople)inatrainingimage,skiptheimage。ifverbose:print(Image{}notsuitablefortraining:{}。format(imgpath,Didntfindafaceiflen(faceboundingboxes)1elseFoundmorethanoneface))else:AddfaceencodingforcurrentimagetothetrainingsetX。append(facerecognition。faceencodings(image,knownfacelocationsfaceboundingboxes)〔0〕)y。append(classdir)DeterminehowmanyneighborstouseforweightingintheKNNclassifierifnneighborsisNone:nneighborsint(round(math。sqrt(len(X))))ifverbose:print(Chosenneighborsautomatically:,nneighbors)CreateandtraintheKNNclassifierknnclfneighbors。KNeighborsClassifier(nneighborsnneighbors,algorithmknnalgo,weightsdistance)knnclf。fit(X,y)SavethetrainedKNNclassifierifmodelsavepathisnotNone:withopen(modelsavepath,wb)asf:pickle。dump(knnclf,f)returnknnclf复制代码使用分类器defpredict(Ximgpath,knnclfNone,modelpathNone,distancethreshold0。6):RecognizesfacesingivenimageusingatrainedKNNclassifier:paramXimgpath:pathtoimagetoberecognized:paramknnclf:(optional)aknnclassifierobject。ifnotspecified,modelsavepathmustbespecified。:parammodelpath:(optional)pathtoapickledknnclassifier。ifnotspecified,modelsavepathmustbeknnclf。:paramdistancethreshold:(optional)distancethresholdforfaceclassification。thelargeritis,themorechanceofmisclassifyinganunknownpersonasaknownone。:return:alistofnamesandfacelocationsfortherecognizedfacesintheimage:〔(name,boundingbox),。。。〕。Forfacesofunrecognizedpersons,thenameunknownwillbereturned。ifnotos。path。isfile(Ximgpath)oros。path。splitext(Ximgpath)〔1〕〔1:〕notinALLOWEDEXTENSIONS:raiseException(Invalidimagepath:{}。format(Ximgpath))ifknnclfisNoneandmodelpathisNone:raiseException(Mustsupplyknnclassifiereitherthourghknnclformodelpath)LoadatrainedKNNmodel(ifonewaspassedin)ifknnclfisNone:withopen(modelpath,rb)asf:knnclfpickle。load(f)LoadimagefileandfindfacelocationsXimgfacerecognition。loadimagefile(Ximgpath)Xfacelocationsfacerecognition。facelocations(Ximg)Ifnofacesarefoundintheimage,returnanemptyresult。iflen(Xfacelocations)0:return〔〕Findencodingsforfacesinthetestiamgefacesencodingsfacerecognition。faceencodings(Ximg,knownfacelocationsXfacelocations)UsetheKNNmodeltofindthebestmatchesforthetestfaceclosestdistancesknnclf。kneighbors(facesencodings,nneighbors1)arematches〔closestdistances〔0〕〔i〕〔0〕distancethresholdforiinrange(len(Xfacelocations))〕Predictclassesandremoveclassificationsthatarentwithinthethresholdreturn〔(pred,loc)ifrecelse(unknown,loc)forpred,loc,recinzip(knnclf。predict(facesencodings),Xfacelocations,arematches)〕复制代码CUDA加速 如果还想提升性能,那就必须上显卡了,由于facerecognition依赖与dlib,因此需要先安装支持CUDA的dlib,可以参考另一篇文章xugaoxiang。com20191213importfacerecognitionimagefacerecognition。loadimagefile(mypicture。jpg)facelocationsfacerecognition。facelocations(image,modelcnn)facelocationsisnowanarraylistingthecoordinatesofeachface!复制代码与摄像头联动 这里使用opencv来实现,opencv读取摄像头每一帧的数据,然后进行resize、颜色空间的转换(由opecv使用的BGR转换成facerecognition使用的RGB),最后进行人脸的检测及识别。importfacerecognitionimportcv2videocapturecv2。VideoCapture(0)Loadasamplepictureandlearnhowtorecognizeit。obamaimagefacerecognition。loadimagefile(obama。jpg)obamafaceencodingfacerecognition。faceencodings(obamaimage)〔0〕Loadasecondsamplepictureandlearnhowtorecognizeit。bidenimagefacerecognition。loadimagefile(biden。jpg)bidenfaceencodingfacerecognition。faceencodings(bidenimage)〔0〕Createarraysofknownfaceencodingsandtheirnamesknownfaceencodings〔obamafaceencoding,bidenfaceencoding〕knownfacenames〔BarackObama,JoeBiden〕Initializesomevariablesfacelocations〔〕faceencodings〔〕facenames〔〕processthisframeTruewhileTrue:Grabasingleframeofvideoret,framevideocapture。read()Resizeframeofvideoto14sizeforfasterfacerecognitionprocessingsmallframecv2。resize(frame,(0,0),fx0。25,fy0。25)ConverttheimagefromBGRcolor(whichOpenCVuses)toRGBcolor(whichfacerecognitionuses)rgbsmallframesmallframe〔:,:,::1〕Onlyprocesseveryotherframeofvideotosavetimeifprocessthisframe:Findallthefacesandfaceencodingsinthecurrentframeofvideofacelocationsfacerecognition。facelocations(rgbsmallframe)faceencodingsfacerecognition。faceencodings(rgbsmallframe,facelocations)facenames〔〕forfaceencodinginfaceencodings:Seeifthefaceisamatchfortheknownface(s)matchesfacerecognition。comparefaces(knownfaceencodings,faceencoding)nameUnknownIfamatchwasfoundinknownfaceencodings,justusethefirstone。ifTrueinmatches:firstmatchindexmatches。index(True)nameknownfacenames〔firstmatchindex〕facenames。append(name)processthisframenotprocessthisframeDisplaytheresultsfor(top,right,bottom,left),nameinzip(facelocations,facenames):Scalebackupfacelocationssincetheframewedetectedinwasscaledto14sizetop4right4bottom4left4Drawaboxaroundthefacecv2。rectangle(frame,(left,top),(right,bottom),(0,0,255),2)Drawalabelwithanamebelowthefacecv2。rectangle(frame,(left,bottom35),(right,bottom),(0,0,255),cv2。FILLED)fontcv2。FONTHERSHEYDUPLEXcv2。putText(frame,name,(left6,bottom6),font,1。0,(255,255,255),1)Displaytheresultingimagecv2。imshow(Video,frame)Hitqonthekeyboardtoquit!ifcv2。waitKey(1)0xFFord(q):breakReleasehandletothewebcamvideocapture。release()cv2。destroyAllWindows()复制代码 另外本地视频文件、网络摄像头(基于rtsp视频流)的识别场景跟本地摄像头的处理非常相似,这里以就不再继续贴代码了。