int main() { std::vector<ContourWithData> allContoursWithData; std::vector<ContourWithData> validContoursWithData; cv::Mat matClassificationFloats; cv::FileStorage fsClassifications("classifications.xml", cv::FileStorage::READ); if (fsClassifications.isOpened() == false) { std::cout << "error, unable to open training classifications file, exiting program\n\n"; return(0); } fsClassifications["classifications"] >> matClassificationFloats; fsClassifications.release(); cv::Mat matTrainingImages; cv::FileStorage fsTrainingImages("images.xml", cv::FileStorage::READ); if (fsTrainingImages.isOpened() == false) { std::cout << "error, unable to open training images file, exiting program\n\n"; return(0); } fsTrainingImages["images"] >> matTrainingImages; fsTrainingImages.release(); cv::Ptr<cv::ml::KNearest> kNearest = cv::ml::KNearest::create(); cv::Ptr<cv::ml::TrainData> trainingData = cv::ml::TrainData::create(matTrainingImages, cv::ml::SampleTypes::ROW_SAMPLE, matClassificationFloats); kNearest->setIsClassifier(true); kNearest->setAlgorithmType(cv::ml::KNearest::Types::BRUTE_FORCE); kNearest->setDefaultK(101); cv::Mat matResults(0, 0, CV_32F); kNearest->train(trainingData); cv::Mat matTestingNumbers = cv::imread("test_numbers.png"); if (matTestingNumbers.empty()) { std::cout << "error: image not read from file\n\n"; return(0); } cv::Mat matGrayscale; cv::Mat matBlurred; cv::Mat matThresh; cv::Mat matThreshCopy; cv::cvtColor(matTestingNumbers, matGrayscale, CV_BGR2GRAY); threshold(matGrayscale, matThresh, 10, 255, CV_THRESH_BINARY_INV); matThreshCopy = matThresh.clone(); std::vector<std::vector<cv::Point> > ptContours; std::vector<cv::Vec4i> v4iHierarchy; cv::findContours(matThreshCopy, ptContours, v4iHierarchy, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_TC89_KCOS); for (int i = 0; i < ptContours.size(); i++) { ContourWithData contourWithData; contourWithData.ptContour = ptContours[i]; contourWithData.boundingRect = cv::boundingRect(contourWithData.ptContour); contourWithData.fltArea = cv::contourArea(contourWithData.ptContour); allContoursWithData.push_back(contourWithData); } for (int i = 0; i < allContoursWithData.size(); i++) { if (allContoursWithData[i].checkIfContourIsValid()) { validContoursWithData.push_back(allContoursWithData[i]); } } std::sort(validContoursWithData.begin(), validContoursWithData.end(), ContourWithData::sortByBoundingRectXPosition); std::string strFinalString; for (int i = 0; i < validContoursWithData.size(); i++) { cv::rectangle(matTestingNumbers, validContoursWithData[i].boundingRect, cv::Scalar(0, 255, 0), 2); cv::Mat matROI = matThresh(validContoursWithData[i].boundingRect); cv::Mat matROIResized; cv::resize(matROI, matROIResized, cv::Size(RESIZED_IMAGE_WIDTH, RESIZED_IMAGE_HEIGHT)); cv::Mat matROIFloat; matROIResized.convertTo(matROIFloat, CV_32FC1); float fltCurrentChar = kNearest->findNearest(matROIFloat.reshape(1,1), kNearest->getDefaultK(), matResults); strFinalString = strFinalString + char(int(fltCurrentChar)); } cv::Mat string_box(100,500,CV_8UC3, cv::Scalar::all(0)); int baseLine = 0; cv::Size string_size = cv::getTextSize(strFinalString, CV_FONT_HERSHEY_DUPLEX, 1, 2, &baseLine); baseLine += 2; cv::Point textOrg((string_box.cols - string_size.width) / 2, (string_box.rows + string_size.height) / 2); cv::putText(string_box, strFinalString, textOrg, CV_FONT_HERSHEY_DUPLEX, 1, cv::Scalar::all(255), 2, 8); cv::imshow("result", string_box); cv::imshow("matTestingNumbers", matTestingNumbers); cv::waitKey(0); return(0); }
int main() { std::vector<ContourWithData> allContoursWithData; // declare empty vectors, std::vector<ContourWithData> validContoursWithData; // we will fill these shortly // read in training classifications /////////////////////////////////////////////////// cv::Mat matClassificationInts; // we will read the classification numbers into this variable as though it is a vector cv::FileStorage fsClassifications("classifications.xml", cv::FileStorage::READ); // open the classifications file if (fsClassifications.isOpened() == false) { // if the file was not opened successfully std::cout << "error, unable to open training classifications file, exiting program\n\n"; // show error message return(0); // and exit program } fsClassifications["classifications"] >> matClassificationInts; // read classifications section into Mat classifications variable fsClassifications.release(); // close the classifications file // read in training images //////////////////////////////////////////////////////////// cv::Mat matTrainingImagesAsFlattenedFloats; // we will read multiple images into this single image variable as though it is a vector cv::FileStorage fsTrainingImages("images.xml", cv::FileStorage::READ); // open the training images file if (fsTrainingImages.isOpened() == false) { // if the file was not opened successfully std::cout << "error, unable to open training images file, exiting program\n\n"; // show error message return(0); // and exit program } fsTrainingImages["images"] >> matTrainingImagesAsFlattenedFloats; // read images section into Mat training images variable fsTrainingImages.release(); // close the traning images file // train ////////////////////////////////////////////////////////////////////////////// cv::Ptr<cv::ml::KNearest> kNearest(cv::ml::KNearest::create()); // instantiate the KNN object // finally we get to the call to train, note that both parameters have to be of type Mat (a single Mat) // even though in reality they are multiple images / numbers kNearest->train(matTrainingImagesAsFlattenedFloats, cv::ml::ROW_SAMPLE, matClassificationInts); // test /////////////////////////////////////////////////////////////////////////////// cv::Mat matTestingNumbers = cv::imread("test1.png"); // read in the test numbers image if (matTestingNumbers.empty()) { // if unable to open image std::cout << "error: image not read from file\n\n"; // show error message on command line return(0); // and exit program } cv::Mat matGrayscale; // cv::Mat matBlurred; // declare more image variables cv::Mat matThresh; // cv::Mat matThreshCopy; // cv::cvtColor(matTestingNumbers, matGrayscale, CV_BGR2GRAY); // convert to grayscale // blur cv::GaussianBlur(matGrayscale, // input image matBlurred, // output image cv::Size(5, 5), // smoothing window width and height in pixels 0); // sigma value, determines how much the image will be blurred, zero makes function choose the sigma value // filter image from grayscale to black and white cv::adaptiveThreshold(matBlurred, // input image matThresh, // output image 255, // make pixels that pass the threshold full white cv::ADAPTIVE_THRESH_GAUSSIAN_C, // use gaussian rather than mean, seems to give better results cv::THRESH_BINARY_INV, // invert so foreground will be white, background will be black 11, // size of a pixel neighborhood used to calculate threshold value 2); // constant subtracted from the mean or weighted mean matThreshCopy = matThresh.clone(); // make a copy of the thresh image, this in necessary b/c findContours modifies the image std::vector<std::vector<cv::Point> > ptContours; // declare a vector for the contours std::vector<cv::Vec4i> v4iHierarchy; // declare a vector for the hierarchy (we won't use this in this program but this may be helpful for reference) cv::findContours(matThreshCopy, // input image, make sure to use a copy since the function will modify this image in the course of finding contours ptContours, // output contours v4iHierarchy, // output hierarchy cv::RETR_EXTERNAL, // retrieve the outermost contours only cv::CHAIN_APPROX_SIMPLE); // compress horizontal, vertical, and diagonal segments and leave only their end points for (int i = 0; i < ptContours.size(); i++) { // for each contour ContourWithData contourWithData; // instantiate a contour with data object contourWithData.ptContour = ptContours[i]; // assign contour to contour with data contourWithData.boundingRect = cv::boundingRect(contourWithData.ptContour); // get the bounding rect contourWithData.fltArea = cv::contourArea(contourWithData.ptContour); // calculate the contour area allContoursWithData.push_back(contourWithData); // add contour with data object to list of all contours with data } for (int i = 0; i < allContoursWithData.size(); i++) { // for all contours if (allContoursWithData[i].checkIfContourIsValid()) { // check if valid validContoursWithData.push_back(allContoursWithData[i]); // if so, append to valid contour list } } // sort contours from left to right std::sort(validContoursWithData.begin(), validContoursWithData.end(), ContourWithData::sortByBoundingRectXPosition); std::string strFinalString; // declare final string, this will have the final number sequence by the end of the program for (int i = 0; i < validContoursWithData.size(); i++) { // for each contour // draw a green rect around the current char cv::rectangle(matTestingNumbers, // draw rectangle on original image validContoursWithData[i].boundingRect, // rect to draw cv::Scalar(0, 255, 0), // green 2); // thickness cv::Mat matROI = matThresh(validContoursWithData[i].boundingRect); // get ROI image of bounding rect cv::Mat matROIResized; cv::resize(matROI, matROIResized, cv::Size(RESIZED_IMAGE_WIDTH, RESIZED_IMAGE_HEIGHT)); // resize image, this will be more consistent for recognition and storage cv::Mat matROIFloat; matROIResized.convertTo(matROIFloat, CV_32FC1); // convert Mat to float, necessary for call to find_nearest cv::Mat matROIFlattenedFloat = matROIFloat.reshape(1, 1); cv::Mat matCurrentChar(0, 0, CV_32F); kNearest->findNearest(matROIFlattenedFloat, 1, matCurrentChar); // finally we can call find_nearest !!! float fltCurrentChar = (float)matCurrentChar.at<float>(0, 0); strFinalString = strFinalString + char(int(fltCurrentChar)); // append current char to full string } std::cout << "\n\n" << "numbers read = " << strFinalString << "\n\n"; // show the full string cv::imshow("matTestingNumbers", matTestingNumbers); // show input image with green boxes drawn around found digits cv::waitKey(0); // wait for user key press return(0); }