<h2>验证OpenCV算法</h2>
<h2>导言</h2>
<p>首先,很抱歉,我花了这么长时间才回复,但实在没有空余时间了。实际上,验证算法是一个非常有趣的话题,并不难。在这篇文章中,我将展示如何验证你的算法(我将使用FaceRecognizer,因为你已经要求它)。和往常一样,我会用一个完整的源代码示例来展示它,因为我认为用代码解释东西要容易得多。</p>
<p>所以每当人们告诉我“我的算法执行不好”时,我都会问他们:</p>
<ul>
<li>实际上什么是坏的?</li>
<li>你是不是看了一个样本就给这个打分了?</li>
<li>你的图像数据是什么?</li>
<li>你如何区分训练和测试数据?</li>
<li>你的标准是什么?</li>
<li>[…]</li>
</ul>
<p>我希望这篇文章能澄清一些困惑,并展示验证算法是多么容易。因为我从计算机视觉和机器学习算法的实验中学到的是:</p>
<ul>
<li>如果没有一个正确的证明,那就是追逐鬼魂。你真的,真的需要数字来谈论。</li>
</ul>
<p>这篇文章中的所有代码都在BSD许可下,所以您可以在项目中使用它。</p>
<h2>验证算法</h2>
<p>任何计算机视觉工程中最重要的任务之一就是获取图像数据。您需要获得与生产中预期的图像数据相同的图像数据,这样您在上线时就不会有任何不好的体验。一个非常实际的例子:如果你想在野外识别人脸,那么在一个非常可控的场景中验证你的算法是没有用的。获取尽可能多的数据,因为<em>数据是最重要的</em>。为了数据。</p>
<p>一旦你有了一些数据,你写了你的算法,它来评估它。有几种验证策略,但我认为您应该从简单的交叉验证开始,然后继续,有关交叉验证的信息,请参见:</p>
<ul>
<li><a href="http://en.wikipedia.org/wiki/Cross-validation_%28statistics%29" rel="noreferrer">Wikipedia on Cross-Validation</a></li>
</ul>
<p>我们将使用<a href="https://github.com/scikit-learn/" rel="noreferrer">scikit-learn</a>这是一个伟大的开源项目,而不是全部由我们自己实现:</p>
<ul>
<li><a href="https://github.com/scikit-learn/" rel="noreferrer">https://github.com/scikit-learn/</a></li>
</ul>
<p>它有一个非常好的文档和教程来验证算法:</p>
<ul>
<li><a href="http://scikit-learn.org/stable/tutorial/statistical_inference/index.html" rel="noreferrer">http://scikit-learn.org/stable/tutorial/statistical_inference/index.html</a></li>
</ul>
<p>所以计划如下:</p>
<ul>
<li>写一个函数来读取一些图像数据。</li>
<li>将<code>cv2.FaceRecognizer</code>包装到scikit学习估计器中。</li>
<li>使用给定的验证和度量来估计<code>cv2.FaceRecognizer</code>的性能。</li>
<li>利润!</li>
</ul>
<h2>正确获取图像数据</h2>
<p>首先,我想在要读取的图像数据上写一些单词,因为这方面的问题几乎总是会弹出。为了简单起见,我在示例中假设图像(您想要识别的<em>人脸</em>,<em>人)是在文件夹中给出的。每人一个文件夹。所以假设我有一个文件夹(一个数据集)调用<code>images</code>,子文件夹有<code>person1</code>,<code>person2</code>等等:</p>
<pre><code>philipp@mango:~/facerec/data/images$ tree -L 2 | head -n 20
.
|-- person1
| |-- 1.jpg
| |-- 2.jpg
| |-- 3.jpg
| |-- 4.jpg
|-- person2
| |-- 1.jpg
| |-- 2.jpg
| |-- 3.jpg
| |-- 4.jpg
[...]
</code></pre>
<p>其中一个公开的可用数据集(已经包含在这样的文件夹结构中)是AT&T Facedatabase,位于:</p>
<ul>
<li><a href="http://www.cl.cam.ac.uk/research/dtg/attarchive/facedatabase.html" rel="noreferrer">http://www.cl.cam.ac.uk/research/dtg/attarchive/facedatabase.html</a></li>
</ul>
<p>一旦解包,它将如下所示(在我的文件系统中,它被解包到<code>/home/philipp/facerec/data/at/</code>,您的路径是不同的!)以下内容:</p>
<pre><code>philipp@mango:~/facerec/data/at$ tree .
.
|-- README
|-- s1
| |-- 1.pgm
| |-- 2.pgm
[...]
| `-- 10.pgm
|-- s2
| |-- 1.pgm
| |-- 2.pgm
[...]
| `-- 10.pgm
|-- s3
| |-- 1.pgm
| |-- 2.pgm
[...]
| `-- 10.pgm
...
40 directories, 401 files
</code></pre>
<h2>把它放在一起</h2>
<p>因此,首先我们将定义一个方法<code>read_images</code>,用于读取图像数据和标签:</p>
<pre><code>import os
import sys
import cv2
import numpy as np
def read_images(path, sz=None):
"""Reads the images in a given folder, resizes images on the fly if size is given.
Args:
path: Path to a folder with subfolders representing the subjects (persons).
sz: A tuple with the size Resizes
Returns:
A list [X,y]
X: The images, which is a Python list of numpy arrays.
y: The corresponding labels (the unique number of the subject, person) in a Python list.
"""
c = 0
X,y = [], []
for dirname, dirnames, filenames in os.walk(path):
for subdirname in dirnames:
subject_path = os.path.join(dirname, subdirname)
for filename in os.listdir(subject_path):
try:
im = cv2.imread(os.path.join(subject_path, filename), cv2.IMREAD_GRAYSCALE)
# resize to given size (if given)
if (sz is not None):
im = cv2.resize(im, sz)
X.append(np.asarray(im, dtype=np.uint8))
y.append(c)
except IOError, (errno, strerror):
print "I/O error({0}): {1}".format(errno, strerror)
except:
print "Unexpected error:", sys.exc_info()[0]
raise
c = c+1
return [X,y]
</code></pre>
<p>然后,读取图像数据变得和调用一样简单:</p>
<pre><code>[X,y] = read_images("/path/to/some/folder")
</code></pre>
<p>因为某些算法(例如特征面、鱼面)要求图像大小相等,所以我添加了第二个参数<code>sz</code>。通过传递元组<code>sz</code>,所有图像都将调整大小。因此,下面的调用将把<code>/path/to/some/folder</code>像素中的所有图像调整为<code>100x100</code>像素</p>
<pre><code>[X,y] = read_images("/path/to/some/folder", (100,100))
</code></pre>
<p>scikit learn中的所有分类器都是从一个<code>BaseEstimator</code>派生的,它应该有一个<code>fit</code>和<code>predict</code>方法d、 <code>fit</code>方法获取样本列表<code>X</code>和相应的标签<code>y</code>,因此映射到<code>cv2.FaceRecognizer</code>的train方法很简单。<code>predict</code>方法还获得一个样本列表和相应的标签,但这次我们需要返回每个样本的预测:</p>
<pre><code>from sklearn.base import BaseEstimator
class FaceRecognizerModel(BaseEstimator):
def __init__(self):
self.model = cv2.createEigenFaceRecognizer()
def fit(self, X, y):
self.model.train(X,y)
def predict(self, T):
return [self.model.predict(T[i]) for i in range(0, T.shape[0])]
</code></pre>
<p>然后,您可以在一系列验证方法和度量标准之间进行选择,以测试<code>cv2.FaceRecognizer</code>。您可以在<a href="https://github.com/scikit-learn/scikit-learn/blob/master/sklearn/cross_validation.py" rel="noreferrer">sklearn.cross_validation</a>中找到可用的交叉验证算法:</p>
<ul>
<li>排除交叉验证</li>
<li>K-折叠交叉验证</li>
<li>分层K-折叠交叉验证</li>
<li>在交叉验证中保留一个标签</li>
<li>替换交叉验证随机抽样</li>
<li>[…]</li>
</ul>
<p>为了估计<code>cv2.FaceRecognizer</code>的识别率,我建议使用分层交叉验证。你可能会问为什么有人需要其他交叉验证方法。假设你想用你的算法进行情感识别。如果你的训练集中有你测试算法的人的图像,会发生什么?你可能会找到最接近的人,但不是情感。在这些情况下,您应该执行独立于主题的交叉验证。</p>
<p>使用scikit learn创建分层k-Fold交叉验证迭代器非常简单:</p>
<pre><code>from sklearn import cross_validation as cval
# Then we create a 10-fold cross validation iterator:
cv = cval.StratifiedKFold(y, 10)
</code></pre>
<p>我们可以从很多指标中选择。现在我只想知道模型的精度,所以我们导入可调用函数<code>sklearn.metrics.precision_score</code>:</p>
<pre><code>from sklearn.metrics import precision_score
</code></pre>
<p>现在我们只需要创建估计器并将<code>estimator</code>、<code>X</code>、<code>y</code>、<code>precision_score</code>和<code>cv</code>传递给<code>sklearn.cross_validation.cross_val_score</code>,它计算我们的交叉验证分数:</p>
<pre><code># Now we'll create a classifier, note we wrap it up in the
# FaceRecognizerModel we have defined in this file. This is
# done, so we can use it in the awesome scikit-learn library:
estimator = FaceRecognizerModel()
# And getting the precision_scores is then as easy as writing:
precision_scores = cval.cross_val_score(estimator, X, y, score_func=precision_score, cv=cv)
</code></pre>
<p>有大量可用的指标,请随意选择其他指标:</p>
<ul>
<li><a href="https://github.com/scikit-learn/scikit-learn/blob/master/sklearn/metrics/metrics.py" rel="noreferrer">https://github.com/scikit-learn/scikit-learn/blob/master/sklearn/metrics/metrics.py</a></li>
</ul>
<p>所以我们把这些都写进剧本里吧!</p>
<h2>验证.py</h2>
<pre><code># Author: Philipp Wagner <bytefish@gmx.de>
# Released to public domain under terms of the BSD Simplified license.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the organization nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# See <http://www.opensource.org/licenses/bsd-license>
import os
import sys
import cv2
import numpy as np
from sklearn import cross_validation as cval
from sklearn.base import BaseEstimator
from sklearn.metrics import precision_score
def read_images(path, sz=None):
"""Reads the images in a given folder, resizes images on the fly if size is given.
Args:
path: Path to a folder with subfolders representing the subjects (persons).
sz: A tuple with the size Resizes
Returns:
A list [X,y]
X: The images, which is a Python list of numpy arrays.
y: The corresponding labels (the unique number of the subject, person) in a Python list.
"""
c = 0
X,y = [], []
for dirname, dirnames, filenames in os.walk(path):
for subdirname in dirnames:
subject_path = os.path.join(dirname, subdirname)
for filename in os.listdir(subject_path):
try:
im = cv2.imread(os.path.join(subject_path, filename), cv2.IMREAD_GRAYSCALE)
# resize to given size (if given)
if (sz is not None):
im = cv2.resize(im, sz)
X.append(np.asarray(im, dtype=np.uint8))
y.append(c)
except IOError, (errno, strerror):
print "I/O error({0}): {1}".format(errno, strerror)
except:
print "Unexpected error:", sys.exc_info()[0]
raise
c = c+1
return [X,y]
class FaceRecognizerModel(BaseEstimator):
def __init__(self):
self.model = cv2.createFisherFaceRecognizer()
def fit(self, X, y):
self.model.train(X,y)
def predict(self, T):
return [self.model.predict(T[i]) for i in range(0, T.shape[0])]
if __name__ == "__main__":
# You'll need at least some images to perform the validation on:
if len(sys.argv) < 2:
print "USAGE: facerec_demo.py </path/to/images> [</path/to/store/images/at>]"
sys.exit()
# Read the images and corresponding labels into X and y.
[X,y] = read_images(sys.argv[1])
# Convert labels to 32bit integers. This is a workaround for 64bit machines,
# because the labels will truncated else. This is fixed in recent OpenCV
# revisions already, I just leave it here for people on older revisions.
#
# Thanks to Leo Dirac for reporting:
y = np.asarray(y, dtype=np.int32)
# Then we create a 10-fold cross validation iterator:
cv = cval.StratifiedKFold(y, 10)
# Now we'll create a classifier, note we wrap it up in the
# FaceRecognizerModel we have defined in this file. This is
# done, so we can use it in the awesome scikit-learn library:
estimator = FaceRecognizerModel()
# And getting the precision_scores is then as easy as writing:
precision_scores = cval.cross_val_score(estimator, X, y, score_func=precision_score, cv=cv)
# Let's print them:
print precision_scores
</code></pre>
<h2>运行脚本</h2>
<p>上面的脚本将打印出Fisherfaces方法的精度分数。您只需使用图像文件夹调用脚本:</p>
<pre><code>philipp@mango:~/src/python$ python validation.py /home/philipp/facerec/data/at
Precision Scores:
[ 1. 0.85 0.925 0.9625 1. 0.9625
0.8875 0.93333333 0.9625 0.925 ]
</code></pre>
<h2>结论</h2>
<p>结论是,使用开源项目会让你的生活变得非常轻松!示例脚本有很多需要改进的地方。您可能需要添加一些日志记录,以查看您所在的文件夹。但这是评估任何您想要的度量的开始,只需阅读scikit学习教程,了解如何执行并使其适应上面的脚本。</p>
<p>我鼓励大家使用OpenCV Python和scikit来学习,因为这两个伟大的项目之间的交互是非常非常容易的。</p>