Python, OpenCV:使用ORB特征和KNN分类器进行性别分类

5

任务: 将人脸图像分类为男性或女性。有带标签的训练图像,从网络摄像头获取测试图像。

使用: Python 2.7, OpenCV 2.4.4

我正在使用ORB从灰度图像提取特征,希望用于训练K-最近邻分类器。每个训练图像都是不同的人,因此每个图像的关键点和描述符数量显然不同。我的问题是,我无法理解OpenCV文档中关于KNN和ORB的内容。我看过其他关于ORB、KNN和FLANN的SO问题,但它们没有什么帮助。

ORB给出的描述符的本质是什么?它与由BRIEF、SURF、SIFT等获得的描述符有何不同?

对于KNN,特征描述符似乎应该对每个训练样本具有相同的大小。如何确保每个图像的描述符大小相同?更一般地说,特征应该以什么格式呈现给具有给定数据和标签的KNN进行训练?数据应该是整型还是浮点型?可以是char吗?

训练数据可以在此处找到。

我还使用了来自OpenCV样本的haarcascade_frontalface_alt.xml

现在,KNN模型只给出10张训练图像以查看我的程序是否能通过没有错误的测试,但它没有通过。

这是我的代码:

import cv2
from numpy import float32 as np.float32

def chooseCascade():
    # TODO: Option for diferent cascades
    # HAAR Classifier for frontal face
    _cascade = cv2.CascadeClassifier('haarcascade_frontalface_alt.xml')
    return _cascade

def cropToObj(cascade,imageFile):
    # Load as 1-channel grayscale image
    image = cv2.imread(imageFile,0)

    # Crop to the object of interest in the image
    objRegion = cascade.detectMultiScale(image) # TODO: What if multiple ojbects in image?

    x1 = objRegion[0,0]
    y1 = objRegion[0,1]
    x1PlusWidth = objRegion[0,0]+objRegion[0,2]
    y1PlusHeight = objRegion[0,1]+objRegion[0,3]

    _objImage = image[y1:y1PlusHeight,x1:x1PlusWidth]

    return _objImage

def recognizer(fileNames):
    # ORB contructor
    orb = cv2.ORB(nfeatures=100)

    keyPoints = []
    descriptors = [] 

    # A cascade for face detection
    haarFaceCascade = chooseCascade()

    # Start processing images
    for imageFile in fileNames:
        # Find faces using the HAAR cascade
        faceImage = cropToObj(haarFaceCascade,imageFile)

        # Extract keypoints and description 
        faceKeyPoints, faceDescriptors = orb.detectAndCompute(faceImage, mask = None)

        #print faceDescriptors.shape
        descRow = faceDescriptors.shape[0]
        descCol = faceDescriptors.shape[1]

        flatFaceDescriptors = faceDescriptors.reshape(descRow*descCol).astype(np.float32)

        keyPoints.append(faceKeyPoints)
        descriptors.append(flatFaceDescriptors)

    print descriptors

    # KNN model and training on descriptors
    responses = []
    for name in fileNames:
        if name.startswith('BF'):
            responses.append(0) # Female
        else:
            responses.append(1) # Male

    knn = cv2.KNearest()
    knnTrainSuccess = knn.train(descriptors,
                                responses,
                                isRegression = False) # isRegression = false, implies classification

    # Obtain test face image from cam
    capture = cv2.VideoCapture(0)
    closeCamera = -1
    while(closeCamera < 0):
        _retval, _camImage = capture.retrieve()      

        # Find face in camera image
        testFaceImage = haarFaceCascade.detectMultiScale(_camImage) # TODO: What if multiple faces?

        # Keyponts and descriptors of test face image
        testFaceKP, testFaceDesc = orb.detectAndCompute(testFaceImage, mask = None)
        testDescRow = testFaceDesc.shape[0]
        flatTestFaceDesc = testFaceDesc.reshape(1,testDescRow*testDescCol).astype(np.float32) 

        # Args in knn.find_nearest: testData, neighborhood
        returnedValue, result, neighborResponse, distance = knn.find_nearest(flatTestFaceDesc,3) 

        print returnedValue, result, neighborResponse, distance


        # Display results
        # TODO: Overlay classification text
        cv2.imshow("testImage", _camImage)

        closeCamera = cv2.waitKey(1)
    cv2.destroyAllWindows()


if __name__ == '__main__':
    fileNames = ['BF09NES_gray.jpg', 
                 'BF11NES_gray.jpg', 
                 'BF13NES_gray.jpg', 
                 'BF14NES_gray.jpg', 
                 'BF18NES_gray.jpg', 
                 'BM25NES_gray.jpg', 
                 'BM26NES_gray.jpg', 
                 'BM29NES_gray.jpg', 
                 'BM31NES_gray.jpg', 
                 'BM34NES_gray.jpg']

    recognizer(fileNames)

目前我在使用knn.train()时遇到了一个错误,它提示descriptors没有被识别为numpy数组。

还有,这种方法完全错误吗?我是否应该使用其他方式进行性别分类? 我对opencv facerec演示中的fisherface和eigenface示例不满意,所以请不要向我推荐使用它们。

非常感谢任何其他帮助。

---编辑---

我尝试了一些方法并得出了一个答案。

我仍然希望SO社区的某个人能够通过提出想法来帮助我,这样我就不必将事情硬编码到我的解决方案中。 我还怀疑knn.match_nearest()没有做我需要它做的事情。

正如预期的那样,识别器并不准确,并且非常容易由于旋转,照明等原因出现误分类。 任何改进此方法的建议都将不胜感激。

我用于训练的数据库是:Karolinska Directed Emotional Faces


一个快速的评论。刚刚发现了BOW。看起来很相关。在这里有一个stackoverflow答案(http://stackoverflow.com/questions/15611872/bow-in-opencv-using-precomputed-features)。还有一个链接(https://groups.google.com/forum/#!topic/accord-net/u5viBhgv0Fw),它说视觉词袋只有一个目的,那就是将可变长度的特征表示转换为固定长度的特征表示。 - Zaw Lin
2个回答

1
我对所描述的方法的有效性/可行性有一些疑虑。这里有另一种方法,您可能需要考虑。 gen 文件夹的内容为 @ http://www1.datafilehost.com/d/0f263abc。当数据量变大时(~10k训练样本),模型的大小可能变得不可接受(~100-200mb)。那么您将需要研究pca / lda等技术。
import cv2
import numpy as np
import os

def feaCnt():
    mat = np.zeros((400,400,3),dtype=np.uint8)
    ret = extr(mat)
    return len(ret)

def extr(img):
    return sobel(img)

def sobel(img):
    gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
    klr = [[-1,0,1],[-2,0,2],[-1,0,1]]
    kbt = [[1,2,1],[0,0,0],[-1,-2,-1]]
    ktb = [[-1,-2,-1],[0,0,0],[1,2,1]]
    krl = [[1,0,-1],[2,0,-2],[1,0,-1]]
    kd1 = [[0,1,2],[-1,0,1],[-2,-1,0]]
    kd2 = [[-2,-1,0],[-1,0,1],[0,1,2]]    
    kd3 = [[0,-1,-2],[1,0,-1],[2,1,0]]
    kd4 = [[2,1,0],[1,0,-1],[0,-1,-2]]
    karr = np.asanyarray([
        klr,
        kbt,
        ktb,
        krl,
        kd1,
        kd2,
        kd3,
        kd4
        ])
    gray=cv2.resize(gray,(40,40))
    res =  np.float32([cv2.resize(cv2.filter2D(gray, -1,k),(15,15)) for k in karr])
    return res.flatten()


root = 'C:/data/gen'

model='c:/data/models/svm/gen.xml'
imgs = []
idx =0
for path, subdirs, files in os.walk(root):
  for name in files:  
    p =path[len(root):].split('\\')
    p.remove('')
    lbl = p[0]
    fpath = os.path.join(path, name)
    imgs.append((fpath,int(lbl)))
    idx+=1

samples = np.zeros((len(imgs),feaCnt()),dtype = np.float32)
labels = np.zeros(len(imgs),dtype = np.float32)

i=0.
for f,l in imgs:
  print i
  img = cv2.imread(f)
  samples[i]=extr(img)
  labels[i]=l
  i+=1

svm = cv2.SVM()
svmparams = dict( kernel_type = cv2.SVM_POLY, 
                       svm_type = cv2.SVM_C_SVC,
                       degree=3.43,
                       gamma=1.5e-4,
                       coef0=1e-1,
                       )
print 'svm train'
svm.train(samples,labels,params=svmparams)
svm.save(model)
print 'done'

result = np.float32( [(svm.predict(s)) for s in samples])
correct=0.
total=0.

for i,j in zip(result,labels):
    total+=1
    if i==j:
      correct+=1
    print '%f'%(correct/total)

非常感谢您的解决方案。我认为您指出使用ORB和KNN模型在更大的训练数据上不实用是正确的。您能告诉我Sobel滤波器和SVM所使用的参数吗?这些参数是从期刊论文中获得还是您自己的研究成果?此外,您能告诉我为什么要将图像调整为(40,40)吗? - samkhan13
我在评论数据大小时并没有提到orb/knn。我是在提到发布的方法。svm参数来自train_auto方法,使用10倍交叉验证集在总大小约为12k的数据上进行。Sobel可能不太科学,但从直觉上看,它提取了8个相等的方向指令(0度、45度、90度等)(虽然不完全准确,但足够接近),使用8个滤波器的滤波器组。这只是一种从图像中获取相位信息的愚蠢方法。你可以用其他东西代替Sobel。这只是让你开始的方式。将大小调整为40x40是为了减少处理时间。 - Zaw Lin
还有另一个resize正在(15, 15)处进行。这是因为你需要固定长度的向量用于SVM,同时也有助于减小最终模型的大小。每个向量的总大小为15x15x8(1800)。这非常大!因此,当训练数据规模增加时,存储的支持向量(每个长度为1800!)将扩大存储的模型大小。这就是为什么在SVM之前可能需要进行PCA / LDA的原因。如果需要数据,请搜索“morph”。由于该任务对种族特征非常敏感,因此如果可能的话,您可能需要确保训练数据只包含同一种族类型(西方人,非裔,亚洲人等)的对象。 - Zaw Lin

1
以前,我曾经苦恼于找不到ORB、SIFT、SURF等技术差异,后来我发现这些SO帖子很有帮助:
- https://dev59.com/X2kw5IYBdhLWcg3wDWTL#10169025 - 有没有快速替代SURF和SIFT进行尺度不变特征提取的方法? - OpenCV ORB特征检测器是如何工作的? 需要注意的最重要的一点是,这些opencv中的特征检测算法需要单通道(通常为8位)灰度图像。
结果发现,knn.train()只能接受数据类型为“32位浮点数”的“数组”。我相信opencv中的SVM训练也有这个要求。在Python中,numpy数组需要每行具有相同的数据类型和相同的形状,而不像Python列表可以具有任何类型和大小的数据。
因此,在生成描述符列表之后,我将该列表转换为数组。
但是!在此之前,我已经将ORB的nfeatures参数硬编码为25。我的所有训练图像大致具有相同的分辨率,并且我能够手动验证每个图像都可以使用ORB产生至少25个关键点。每个关键点有32个描述符,因此每张脸部图像有800个描述符。ORB返回一个元素为整数类型的数组,其行数等于关键点的数量。我将其重塑为一个描述符的单行以生成大小为800的“向量”。
下一个挑战是使用knn.find_nearest()。它需要一个“矩阵”,其行与传递给knn.train()的ndarray的行形状相同。否则可能会产生错误:
OpenCV Error: Bad argument (Input samples must be floating-point matrix (<num_samples>x<var_count>)) in find_nearest

即使您只有一个向量需要传递给 knn.find_nearest(),它也必须是形状为1xm的向量,其中m是向量中元素的数量。
因此,我不得不想出一种粗略的方法来检查我的网络摄像头拍摄的图像是否可在我硬编码的方法中使用。
现在代码看起来像这样:
import cv2
import numpy as np

def chooseCascade():
    # TODO: Option for diferent cascades
    # HAAR Classifier for frontal face
    _cascade = cv2.CascadeClassifier('haarcascade_frontalface_alt.xml')
    return _cascade

def cropToObj(cascade,imageFile,flag):
    if flag == 0:
        # Load as 1-channel grayscale image
        image = cv2.imread(imageFile,0)
    elif flag == 1:
        # Load as 3-channel color image
        image = cv2.imread(imageFile,1)
    elif flag == -1: 
        # Load image as is 
        image = cv2.imread(imageFile,-1)
    elif flag == 2:
        # Image is from camera
        image = imageFile
    else:
        print 'improper arguments passed to cropToObj'

    # Crop to the object of interest in the image
    objRegion = cascade.detectMultiScale(image) # TODO: What if multiple ojbects in image?

    x1 = objRegion[0,0]
    y1 = objRegion[0,1]
    x1PlusWidth = objRegion[0,0]+objRegion[0,2]
    y1PlusHeight = objRegion[0,1]+objRegion[0,3]

    objImage = image[y1:y1PlusHeight,x1:x1PlusWidth]

    return objImage

def recognizer(fileNames):
    # ORB contructor
    orb = cv2.ORB(nfeatures=25)

    keyPoints = []
    descriptors = [] 

    # A cascade for face detection
    haarFaceCascade = chooseCascade()

    # Start processing images
    for imageFile in fileNames:
        # Find faces using the HAAR cascade
        faceImage = cropToObj(haarFaceCascade,imageFile,flag)

        # Extract keypoints and description 
        faceKeyPoints, faceDescriptors = orb.detectAndCompute(faceImage, mask = None)

        #print faceDescriptors.shape
        descRow = faceDescriptors.shape[0]
        descCol = faceDescriptors.shape[1]

        flatFaceDescriptors = faceDescriptors.reshape(descRow*descCol)

        keyPoints.append(faceKeyPoints)
        descriptors.append(flatFaceDescriptors)

    descriptors = np.asarray(descriptors, dtype=np.float32)

    # KNN model and training on descriptors
    responses = []
    for name in fileNames:
        if name.startswith('BF'):
            responses.append(0) # Female
        else:
            responses.append(1) # Male

    responses = np.asarray(responses)

    knn = cv2.KNearest()
    knnTrainSuccess = knn.train(descriptors,
                                responses,
                                isRegression = False) # isRegression = false, implies classification

    # Obtain test face image from cam
    capture = cv2.VideoCapture(0)
    closeCamera = -1
    while(closeCamera < 0):
        retval, camImage = capture.read()      

        # Find face in camera image
        try:
            testFaceImage = cropToObj(haarFaceCascade, camImage, 2) # TODO: What if multiple faces?
            testFaceImage = cv2.cvtColor(testFaceImage, cv2.COLOR_BGR2GRAY)
        except TypeError:
            print 'check if front face is visible to camera'
            pass

        # Keyponts and descriptors of test face image
        testFaceKP, testFaceDesc = orb.detectAndCompute(testFaceImage, mask = None)
        testDescRow = testFaceDesc.shape[0]
        testDescCol = testFaceDesc.shape[1]
        flatTestFaceDesc = testFaceDesc.reshape(1,testDescRow*testDescCol)
        flatTestFaceDesc = np.asarray(flatTestFaceDesc,dtype=np.float32) 

        if flatTestFaceDesc.size == 800:
            # Args in knn.find_nearest: testData, neighborhood
            returnedValue, result, neighborResponse, distance = knn.find_nearest(flatTestFaceDesc,5)
            if returnedValue == 0.0:
                print 'Female'
            else:
                print 'Male'
        else: 
            print 'insufficient size of image' 

        # Display results
        # TODO: Overlay classification text
        cv2.imshow("testImage", camImage)

        closeCamera = cv2.waitKey(1)
    cv2.destroyAllWindows()


if __name__ == '__main__':
    fileNames = ['BF09NES_gray.jpg', 
                 'BF11NES_gray.jpg', 
                 'BF13NES_gray.jpg', 
                 'BF14NES_gray.jpg', 
                 'BF18NES_gray.jpg', 
                 'BM25NES_gray.jpg', 
                 'BM26NES_gray.jpg', 
                 'BM29NES_gray.jpg', 
                 'BM31NES_gray.jpg', 
                 'BM34NES_gray.jpg']

    recognizer(fileNames)

我还希望SO社区中的某个人能够提出一个想法,以便我不必将东西硬编码到我的解决方案中。我也怀疑knn.match_nearest()没有做我需要它做的事情。
正如所预期的那样,识别器并不准确,并且非常容易因旋转、光照等原因而导致误分类。如何改善这种方法的建议将不胜感激。

一个快速的评论。刚发现BOW。看起来很相关。在stackoverflow有答案(http://stackoverflow.com/questions/15611872/bow-in-opencv-using-precomputed-features)。并且在这里(https://groups.google.com/forum/#!topic/accord-net/u5viBhgv0Fw),它说:“视觉词袋(Bag of Visual Words)只有一个目的,那就是将可变长度的特征表示转换成固定长度的特征表示。” - Zaw Lin
@ZawLin 感谢您的评论。如果您将其粘贴为答案,我可以接受它。 - samkhan13

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接