利用opencv的KNN识别数字,可以用在很多编码扫描上。第一次写c++ 边试边写的 很糙
结果:
效果还可以 但是对裁剪的准确性要求较高。
需要配置好opencv的环境
step 1 :切分训练数据
int split_data() { Mat src, dst; src = imread("D:/Works/KNN-letters/my.png"); if (src.empty()) { std::cout << "can not load image " << std::endl; return -1; } imshow("input", src); dst = src.clone(); cvtColor(src, src, COLOR_BGR2GRAY); Mat bin = src.clone(); Mat ROI = src(Rect(0, 0, src.size().width, src.size().height)); blur(ROI, ROI, Size(5, 5)); imshow("blur", ROI); threshold(ROI, ROI, 220, 255, THRESH_BINARY); Canny(ROI, ROI, 20, 80, 3, false); std::vector<std::vector<Point>> contours; std::vector<Vec4i>hierarchy; imshow("ROI", ROI); findContours(ROI, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE, Point(0, 0)); std::cout << contours.size() << std::endl; RNG rng(0); std::vector<RotatedRect> minRects(contours.size()); std::vector<float> height, width; for (int i = 0; i < contours.size(); i++) { Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255)); Rect rect = boundingRect(contours[i]); height.push_back(rect.height); width.push_back(rect.width); } //获取字符外接矩形宽高的最大值 std::vector<float>::iterator h = std::max_element(std::begin(height), std::end(height)); std::vector<float>::iterator w = std::max_element(std::begin(width), std::end(width)); //获得轮廓的外接矩形 std::vector<Rect>rects; //将轮廓外接矩形按纵坐标排序 std::vector<float>sequence; for (int i = 0; i < contours.size(); i++) { Scalar color = Scalar(0, rng.uniform(0, 255), rng.uniform(0, 255)); Rect rect = boundingRect(contours[i]); Rect reRect = Rect(Point(rect.x + rect.width / 2.0 - *w / 2.0, rect.y + rect.height / 2.0 - *h / 2.0 ), Point(rect.x + rect.width / 2.0 + *w / 2.0, rect.y + rect.height / 2.0 + *h / 2.0)); rectangle(dst, reRect, color, 2); rects.push_back(reRect); sequence.push_back(reRect.y); } //按纵坐标排序 sort(sequence.begin(), sequence.end()); threshold(bin, bin, 0, 255, THRESH_BINARY | THRESH_OTSU); std::cout << rects.size() << std::endl; int n = 0; for (int i = 0; i < rects.size(); i++) { for (int j = 0; j < 10; j++) { std::string outPath = "D:/Works/KNN-letters/letters3/"; char label = '0'; char temp[256]; if ((rects[i].y > sequence[j * 6] - *h / 2.0) && (rects[i].y < sequence[j * 6] + *h / 2.0)) { label = label + j; sprintf_s(temp, "%d", n); outPath = outPath + label + "/" + temp + ".jpg"; std::cout << outPath << std::endl; //imwrite(outPath, bin(rects[i])); n++; } } } imshow("output", dst); waitKey(); waitKey(); return 0; }
注意分类文件夹寻妖自己创建
step 2 训练KNN
int trian_my2() { //split_data(); ////===============================读取训练数据===============================//// //图片共有10类 const int classSum = 10; //每类共50张图片 const int imagesSum = 6; //图片尺寸 const int imageRows = 17; const int imageCols = 12; //每一行一个训练图片 float trainingData[classSum * imagesSum][imageRows * imageCols] = { {0} }; //训练样本标签 float labels[classSum * imagesSum] = { 0 }; for (int i = 0; i < classSum; i++) { //目标文件夹路径 std::string inPath = "D:/Works/KNN-letters/letters3/"; char label = '0'; int k = 0; label = label + i; inPath = inPath + label + "/*.jpg"; std::cout << inPath << std::endl; //用于查找的句柄 _int64 handle; struct _finddata_t fileinfo; int r; //第一次查找 handle = _findfirst(inPath.c_str(), &fileinfo); if (handle == -1) return -1; do { //找到的文件的文件名 std::string imgname = "D:/Works/KNN-letters/letters3/"; imgname = imgname + label + "/" + fileinfo.name; std::cout<<imgname<<std::endl; Mat src = imread(imgname, 0); if (src.empty()) { std::cout << "can not load image " << std::endl; return -1; } //序列化后放入作为样本矩阵的一行 for (int j = 0; j < imageRows * imageCols; j++) { trainingData[i * imagesSum + k][j] = (float)src.data[j]; } // 设置样本标签 labels[i * imagesSum + k] = label; k++; std::cout << label << std::endl; } while (!_findnext(handle, &fileinfo)); _findclose(handle); } Mat trainingDataMat(classSum * imagesSum, imageRows * imageCols, CV_32FC1, trainingData); Mat labelsMat(classSum * imagesSum, 1, CV_32FC1, labels); //std::cout<<trainingDataMat<<std::endl; //std::cout<<labelsMat<<std::endl; ////===============================创建KNN模型===============================//// Ptr<ml::KNearest> model = ml::KNearest::create(); model->setDefaultK(3); model->setIsClassifier(true); Ptr<TrainData>trainData = TrainData::create(trainingDataMat, ROW_SAMPLE, labelsMat); model->train(trainData); model->save("D:/Works/KNN-letters/KNN_NUM.xml"); ////===============================预测部分===============================//// //Ptr<ml::KNearest> model = StatModel::load<KNearest>("D:/Works/KNN-letters/KNN_NUM.xml"); Mat src, dst; src = imread("D:/Works/KNN-letters/my.png"); if (src.empty()) { std::cout << "can not load image " << std::endl; return -1; } dst = src.clone(); //创建感兴趣区域,选取右侧10列作为预测数据 cvtColor(src, src, COLOR_BGR2GRAY); blur(src, src, Size(5, 5)); threshold(src, src, 230, 255, THRESH_BINARY); Canny(src, src, 20, 80, 3, false); std::vector<std::vector<Point>> contours; std::vector<Vec4i>hierarchy; findContours(src, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE, Point(0, 0)); for (int i = 0; i < contours.size(); i++) { Rect rect = boundingRect(contours[i]); //以矩形中心及指定的宽高作为字符区域 Rect reRect = Rect(Point(rect.x + rect.width / 2.0 - imageCols / 2.0, rect.y + rect.height / 2.0 - imageRows / 2.0), Point(rect.x + rect.width / 2.0 + imageCols / 2.0, rect.y + rect.height / 2.0 + imageRows / 2.0)); Mat sampleImg; cvtColor(dst, sampleImg, COLOR_BGR2GRAY); threshold(sampleImg, sampleImg, 0, 255, THRESH_BINARY | THRESH_OTSU); Mat sample = Mat::zeros(Size(imageCols, imageRows), sampleImg.type()); float sampleData[imageRows * imageCols]; int nub = 0; for (int r = 0; r < imageRows; r++) { for (int c = 0; c < imageCols; c++) { sampleData[nub] = sampleImg.at<uchar>(reRect.y + r, reRect.x + c); nub++; } } Mat sampleDataMat(1, imageRows * imageCols, CV_32FC1, sampleData); char f; f = model->predict(sampleDataMat); char temp[256]; sprintf_s(temp, "%c", f); std::cout << temp << " " << std::endl; std::string text(temp); RNG rng(f); Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255)); putText(dst, text, Point(reRect.x, reRect.y + reRect.height), 1, 1.5, color, 2); } imshow("output", dst); waitKey(); return 0; }
代码粘贴下去可以直接使用;