KNN(K近邻算法)是一种经典的机器学习算法,既可用于分类也可用于回归预测。该算法的核心思路是在训练样本中寻找与待分类样本距离最近的K个样本。如果目的是分类,则统计这K个样本中各类别的数量,数量最多的类别即视为待分类样本的类别;如果是回归预测,则计算这K个样本的平均值作为预测值。
KNN算法可以应用于图像识别,因为图像识别本质上也是一种分类任务。本文将首先介绍KNN算法的基本原理,然后讲解如何在OpenCV中使用KNN算法模块,并通过实例展示如何利用KNN算法对手写数字图像进行识别。
训练样本:这是预先准备好的数据集,包含所有可能的类别,每个数据都有一个唯一的标签来标识其类别。例如,待分类的图像可能属于鸟、狗、马、飞机或班车等5种类别,每个类别都有相应的图像,并附有标签。
待分类样本:这是需要分类的新数据,例如一张图像。将这张图像输入KNN算法中,算法会对其进行分类并给出类别标签。
样本距离:通常,每个样本都被表示为一个向量。衡量向量之间距离的方法有多种,如欧氏距离、余弦距离、汉明距离等。其中,欧氏距离是最常用的一种距离度量方法。
K值:K值决定了在训练样本中寻找与待分类样本距离最近的样本数量。例如,如果K值设为5,那么对于每一个待分类样本,都会找到5个与其距离最近的样本,然后统计这些样本中各类别的数量,数量最多的类别即为待分类样本的类别。K值的选择通常在5到10之间,具体数值需要根据实际应用效果进行调整。
假设有一个待分类样本,其可能的类别为矩形、圆形和菱形。设K值为6,从所有训练样本中找到与待分类样本最接近的6个样本,分别是样本1、样本2、样本3、样本4、样本5和样本6。对这6个样本进行分类统计,发现其中有3个矩形、2个菱形和1个圆形。由于矩形数量最多,因此可以判断待分类样本为矩形。
OpenCV 3.4.1已经实现了KNN算法,并将其封装为类。使用时只需调用相关接口并传入参数即可获得分类结果。具体步骤如下:
创建KNN类并设置参数:
cpp
const int K = 3; // 设置K值为3
cv::Ptr<cv::ml::KNearest> knn = cv::ml::KNearest::create();
knn->setDefaultK(K); // 设置KNN类的K值
knn->setIsClassifier(true); // 设置KNN用于分类
knn->setAlgorithmType(cv::ml::KNearest::BRUTE_FORCE); // 设置寻找距离最近的K个样本的方式为暴力破解
输入训练数据及其标签:
cpp
Mat traindata;
Mat trainlabel;
traindata.push_back(srcimage); // 将训练图像保存到训练矩阵中
trainlabel.push_back(i); // 将标签保存到标签矩阵中
traindata.convertTo(traindata, CV_32F); // 训练矩阵必须是浮点型数据
将训练数据和标签输入KNN模块进行训练:
cpp
knn->train(traindata, cv::ml::ROW_SAMPLE, trainlabel);
对待分类图像进行分类:
cpp
Mat result;
int response = knn->findNearest(testdata, K, result);
在OpenCV 3.4.1的samples/data目录中有一张1000x2000的手写数字图像,该图像包含5000个20x20的小图像块,每个小图像块代表一个手写数字。首先编写程序将这张图像分割成5000个20x20的图像块,并将相同的数字保存到同一文件夹中。
cpp
void read_digital_img() {
char ad[128] = {0};
int filename = 0, filenum = 0;
Mat img = imread("digits.png");
Mat gray;
cvtColor(img, gray, CV_BGR2GRAY);
int b = 20;
int m = gray.rows / b; // 原图为1000x2000
int n = gray.cols / b; // 裁剪为5000个20x20的小图块
for (int i = 0; i < m; i++) {
int offsetRow = i * b; // 行上的偏移量
if (i % 5 == 0 && i != 0) {
filename++;
filenum = 0;
}
for (int j = 0; j < n; j++) {
int offsetCol = j * b; // 列上的偏移量
sprintf_s(ad, "%d/%d.jpg", filename, filenum++);
Mat tmp;
gray(Range(offsetRow, offsetRow + b), Range(offsetCol, offsetCol + b)).copyTo(tmp);
imwrite(ad, tmp);
}
}
}
运行上述代码后,从0到9的文件夹中分别保存了对应数字的小图像块。接下来,使用每个文件夹下的前400张图像作为训练数据,对KNN模型进行训练,然后使用该模型对每个文件夹下的后100张图像进行分类,并统计分类结果的准确率。
```cpp
void KNNtest() {
char ad[128] = {0};
int testnum = 0, truenum = 0;
const int K = 3;
cv::Ptr
Mat traindata, trainlabel;
for (int i = 0; i < 10; i++) {
for (int j = 0; j < 400; j++) {
sprintf_s(ad, "%d/%d.jpg", i, j);
Mat srcimage = imread(ad);
srcimage = srcimage.reshape(1, 1);
traindata.push_back(srcimage);
trainlabel.push_back(i);
}
}
traindata.convertTo(traindata, CV_32F);
knn->train(traindata, cv::ml::ROW_SAMPLE, trainlabel);
for (int i = 0; i < 10; i++) {
for (int j = 400; j < 500; j++) {
testnum++;
sprintf_s(ad, "%d/%d.jpg", i, j);
Mat testdata = imread(ad);
testdata = testdata.reshape(1, 1);
testdata.convertTo(testdata, CV_32F);
Mat result;
int response = knn->findNearest(testdata, K, result);
if (response == i) {
truenum++;
}
}
}
cout << "测试总数: " << testnum << endl;
cout << "正确分类数: " << truenum << endl;
cout << "准确率: " << (float)truenum / testnum * 100 << "%" << endl;
} ```
运行上述代码后,可以看到KNN算法对手写数字图像的识别(分类)准确率较高。这是因为手写数字图像包含的特征相对简单,而KNN算法在处理简单特征时表现良好。然而,对于复杂图像,KNN算法的识别准确率可能会较低。在后续文章中,我们将探讨如何使用KNN算法识别更复杂的图像,并尝试提高识别准确率。