[OpenCV3.4.3] KNN使用教程

// 参考资料:(建议先阅读此处)
// https://blog.****.net/chaipp0607/article/details/77966888
// https://stackoverflow.com/questions/28035484/opencv-3-knn-implementation
#include <iostream>
#include <string>
#include <opencv2/ml.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>


void Init()
{
    std::cout << "Init..." << std::endl;

    // 我们使用的训练样本是OpenCV自带的一张图,在\opencv\sources\samples\data目录下
    // 我把这张图拷贝到我的桌面上了,大家可以自行修改路径
    auto Img = cv::imread(R"(C:\Users\Crablet\Desktop\digits.png)");
    decltype(Img) Gray;
    cv::cvtColor(Img, Gray, CV_BGR2GRAY);

    constexpr auto b = 20;
    const auto m = Gray.rows / b, n = Gray.cols / b;
    auto FileName = 0, FileNum = 0;
    for (int i = 0; i < m; ++i)
    {
        if (i % 5 == 0 && i != 0)
        {
            ++FileName;
            FileNum = 0;
        }

        const auto OffsetRow = i * b;
        for (int j = 0; j < n; ++j)
        {
            const auto OffsetCol = j * b;
            const auto Address 
                = R"(C:\Users\Crablet\Desktop\Out\)"
                + std::to_string(FileName)
                + std::to_string(FileNum++)
                + R"(.jpg)";
            decltype(Gray) Tmp;
            Gray(cv::Range(OffsetRow, OffsetRow + b), cv::Range(OffsetCol, OffsetCol + b))
                .copyTo(Tmp);
            cv::imwrite(Address, Tmp);
        }
    }

    std::cout << "Init done.\n" << std::endl;
}

void TrainAndPredict(int k)
{
    std::cout << "Training..." << std::endl;

    cv::Mat TrainData, TrainLabel;
    for (int i = 0; i < 10; ++i)
    {
        for (int j = 0; j < 400; ++j)
        {
            const auto Address
                = R"(C:\Users\Crablet\Desktop\Out\)"
                + std::to_string(i)
                + std::to_string(j)
                + R"(.jpg)";
            const auto Src = cv::imread(Address).reshape(1, 1);
            TrainData.push_back(Src);
            TrainLabel.push_back(i);
        }
    }
    TrainData.convertTo(TrainData, CV_32F);

    // 这是新版OpenCV的接口,网上很多资料都旧了,所以我就写一篇文章介绍一下新版接口的用法
    // 建议先阅读“参考资料”中的内容
    auto KNN = cv::ml::KNearest::create();
    KNN->setDefaultK(k);
    KNN->setIsClassifier(true);
    KNN->setAlgorithmType(cv::ml::KNearest::BRUTE_FORCE);
    KNN->train(TrainData, cv::ml::ROW_SAMPLE, TrainLabel);

    std::cout << "Predicting..." << std::endl;

    auto TestNum = 0, TrueNum = 0;
    for (int i = 0; i < 10; ++i)
    {
        for (int j = 400; j < 500; ++j)
        {
            ++TestNum;

            const auto Address
                = R"(C:\Users\Crablet\Desktop\Out\)"
                + std::to_string(i)
                + std::to_string(j)
                + R"(.jpg)";
            auto TestData = cv::imread(Address).reshape(1, 1);
            TestData.convertTo(TestData, CV_32F);

            // 这个Dummy只是为了占个位,我们并不需要它的内容,因为我们只想读取KNN->findNearest的返回值而已
            // 注意:“auto Response = KNN->findNearest(TestData, KNN->getDefaultK(), cv::Mat());”这样写是不行的
            // 所以我们才需要这个Dummy
            cv::Mat Dummy;  
            auto Response = KNN->findNearest(TestData, KNN->getDefaultK(), Dummy);
            if (static_cast<int>(Response) == i)	// 检测是否成功识别
            {
                ++TrueNum;
            }
        }
    }

    std::cout << "K: " << k << std::endl;
    std::cout << "TrueNum: " << TrueNum << std::endl;
    std::cout << "TestNum: " << TestNum << std::endl;
    std::cout << "Result: " << 1.0 * TrueNum / TestNum << std::endl;
    std::cout << std::endl;
}

int main()
{
    Init();

    for (int i = 1; i <= 10; ++i)
    {
        TrainAndPredict(i);
    }

    return 0;
}

效果图:
[OpenCV3.4.3] KNN使用教程