KNN分类器数字识别前的预处理

15

目前,我正在使用OpenCV创建数字识别系统。在网上(甚至在StackOverflow上)有许多文章和示例。我决定使用KNN分类器,因为这个解决方案是WEB上最流行的。我发现了一个手写数字数据库,其中训练集包含60k个实例,误差率小于5%。

我使用这个教程作为如何使用OpenCV处理这个数据库的示例。我正在使用完全相同的技术,在测试数据(t10k-images.idx3-ubyte)上得到了4%的误差率。但是当我尝试对自己的数字进行分类时,误差率要大得多。例如:

  • enter image description here被识别为7
  • enter image description hereenter image description here 被识别为5
  • enter image description hereenter image description here被识别为1
  • enter image description here被识别为8

等等(如果需要,我可以上传所有图片)。

正如您所看到的,所有数字都具有良好的质量,并且对人类来说容易识别。

所以我决定在分类之前进行一些预处理。根据MNIST数据库站点上的表格,我发现人们正在使用去倾斜,去噪,模糊和像素移位技术。不幸的是,几乎所有文章的链接都失效了。因此,我决定自己进行这样的预处理,因为我已经知道如何做。

现在,我的算法如下:

  1. 侵蚀图像(我认为我的原始数字太粗糙了)。
  2. 删除小轮廓。
  3. 阈值和模糊图像。
  4. 居中数字(而不是移动)。

我认为在我的情况下不需要去倾斜,因为所有数字都通常旋转。而且我也不知道如何找到正确的旋转角度。所以处理后我得到了这些图片:

  • enter image description here 也是 1
  • enter image description here3(不是以前的5
  • enter image description here5(不是8
  • List item7(收获!)

因此,这样的预处理帮助了我一些,但我需要更好的结果,因为在我看来,这些数字应该没有问题地被识别出来。

有人能给我任何有关预处理的建议吗?感谢任何帮助。

P.S. 我可以上传我的源代码(c ++)。


3
你的训练数据是手写数字,但这些是印刷数字。也许改用印刷数字来进行训练? - David Brown
@DavidBrown 我考虑过这个问题,但我在哪里可以找到这样大(60k)的数据库呢?自己创建吗? - ArtemStorozhuk
2
@ArtemStorozhuk,请使用计算机上安装的字体作为训练集。 - John
@John 哇,我不知道我可以在我的电脑上找到它们!一直以为只能用谷歌。你认为我自己能训练出这样强大的数据库吗?手写数字和普通印刷数字有很大的区别吗? - ArtemStorozhuk
@ArtemStorozhuk,你的结果是非常不同的强有力的证据。 - John
@DavidBrown 感谢您的提示 - 它帮助我解决了我的问题(如果您想查看答案,请查看)。 - ArtemStorozhuk
3个回答

4
我意识到了我的错误——它与预处理毫无关联(感谢@DavidBrown@John)。我使用的是手写数字数据集,而不是印刷体(大写字母)。我在网上找不到这样的数据库,所以我决定自己创建它。我已经将我的数据库上传到Google Drive

以下是你如何使用它进行训练和分类:

int digitSize = 16;
//returns list of files in specific directory
static vector<string> getListFiles(const string& dirPath)
{
    vector<string> result;
    DIR *dir;
    struct dirent *ent;
    if ((dir = opendir(dirPath.c_str())) != NULL)
    {
        while ((ent = readdir (dir)) != NULL)
        {
            if (strcmp(ent->d_name, ".") != 0 && strcmp(ent->d_name, "..") != 0 )
            {
                result.push_back(ent->d_name);
            }
        }
        closedir(dir);
    }
    return result;
}

void DigitClassifier::train(const string& imagesPath)
{
    int num = 510;
    int size = digitSize * digitSize;
    Mat trainData = Mat(Size(size, num), CV_32FC1);
    Mat responces = Mat(Size(1, num), CV_32FC1);

    int counter = 0;
    for (int i=1; i<=9; i++)
    {
        char digit[2];
        sprintf(digit, "%d/", i);
        string digitPath(digit);
        digitPath = imagesPath + digitPath;
        vector<string> images = getListFiles(digitPath);
        for (int j=0; j<images.size(); j++)
        {
            Mat mat = imread(digitPath+images[j], 0);
            resize(mat, mat, Size(digitSize, digitSize));
            mat.convertTo(mat, CV_32FC1);
            mat = mat.reshape(1,1);
            for (int k=0; k<size; k++)
            {
                trainData.at<float>(counter*size+k) = mat.at<float>(k);
            }
            responces.at<float>(counter) = i;
            counter++;
        }
    }
    knn.train(trainData, responces);
}

int DigitClassifier::classify(const Mat& img) const
{
    Mat tmp = img.clone();

    resize(tmp, tmp, Size(digitSize, digitSize));

    tmp.convertTo(tmp, CV_32FC1);

    return knn.find_nearest(tmp.reshape(1, 1), 5);
}

1

5 & 6, 1 & 7, 9 & 8被认为是相同的,因为类的中心点太相似了。这是怎么回事?

  • 对数字应用连通组件标记法以获取数字的真实边界,并在这些边界上裁剪图像。这样,您将在更正确的区域上工作,并且中心点被规范化。
  • 然后按水平方向将数字分成两部分。(例如,在分割“8”后,您将有两个圆)

结果,“9”和“8”以及“5”和“6”更容易识别。上半部分将相同,但下半部分不同。


0

我无法给你比你自己的答案更好的答案,但我想提供一些建议。您可以通过以下方式改进数字识别系统:

  • 在白色和黑色补丁上应用骨架化处理

  • 之后,应用距离变换

通过这种方式,当数字不完全居中或形态不完全相同时,您可以提高分类器的结果。


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