16

I try to recognize the characters of license plates using OCR, but my licence plate have worse quality. enter image description here

I'm trying to somehow improve character recognition for OCR, but my best result is this:result. enter image description here

And even tesseract on this picture does not recognize any character. My code is:

#include <cv.h>         // open cv general include file
#include <highgui.h>    // open cv GUI include file
#include <iostream>     // standard C++ I/O
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <string>

using namespace cv;

int main( int argc, char** argv )
{
    Mat src;
    Mat dst;

    Mat const structure_elem = getStructuringElement(
                         MORPH_RECT, Size(2,2));

    src = imread(argv[1], CV_LOAD_IMAGE_COLOR);   // Read the file

    cvtColor(src,src,CV_BGR2GRAY);
    imshow( "plate", src );

    GaussianBlur(src, src, Size(1,1), 1.5, 1.5);
    imshow( "blur", src );

    equalizeHist(src, src);
    imshow( "equalize", src );

    adaptiveThreshold(src, src, 255, ADAPTIVE_THRESH_GAUSSIAN_C, CV_THRESH_BINARY, 15, -1);
    imshow( "threshold", src );

    morphologyEx(src, src, MORPH_CLOSE, structure_elem);
    imshow( "morphological operation", src );

    imwrite("end.jpg", src);

    waitKey(0);
    return 0;
}

And my question is, do you know how to achieve better results? More clear image? Despite having my licence plate worse quality, so that the result could read OCR (for example Tesseract).

Thank you for answers. Really I do not know how to do it.

Konrad Lindenbach
  • 4,911
  • 1
  • 26
  • 28
Boidil
  • 217
  • 1
  • 2
  • 8
  • 1
    http://community.aiim.org/blogs/richard-medina/2013/03/15/10-ways-to-improve-capture-ocr-and-indexing – Khalil Khalaf Apr 04 '16 at 16:21
  • 2
    Any chance to start with a higher resolution input? Probably better illumination and faster exposure. Maybe a better shot would be to try doing the recognition yourself. If you just want to focus on CZ plates, there will be just one font to deal with. For most plates (other than the new custom ones) you can make some assumptions about what symbols can be in given position -- e.g. the last 4 will most likely be only 0-9, the second is probably a letter, etc. You can try several attempts with different limitations and see what gives the best match. – Dan Mašek Apr 04 '16 at 19:49
  • 1
    I don't think, that I'll do a better input. I focus on only CZ plates, but since 2016 may have a plates almost all characters, so I try to find a procedure that cleans up the image and then I'll be able to read the characters. You would know how to do it? – Boidil Apr 04 '16 at 20:14

1 Answers1

37

One possible algorithm to clean up the images is as follows:

  • Scale the image up, so that the letters are more substantial.
  • Reduce the image to only 8 colours by k-means clustering.
  • Threshold the image, and erode it to fill in any small gaps and make the letters more substantial.
  • Invert the image to make masking easier.
  • Create a blank mask image of the same size, set to all zeros
  • Find contours in the image. For each contour:
    • Find bounding box of the contour
    • Find the area of the bounding box
    • If the area is too small or too large, drop the contour (I chose 1000 and 10000 as limits)
    • Otherwise draw a filled rectangle corresponding to the bounding box on the mask with white colour (255)
    • Store the bounding box and the corresponding image ROI
  • For each separated character (bounding box + image)
    • Recognise the character

Note: I prototyped this in Python 2.7 with OpenCV 3.1. C++ ports of this code are near the end of this answer.


Character Recognition

I took inspiration for the character recognition from this question on SO.

Then I found an image that we can use to extract training images for the correct font. I cut them down to only include digits and letters without accents.

train_digits.png:

train_digits.png

train_letters.png:

train_letters.png

Then i wrote a script that splits the individual characters, scales them up and prepares the training images that contain single character per file:

import os
import cv2
import numpy as np

# ============================================================================    

def extract_chars(img):
    bw_image = cv2.bitwise_not(img)
    contours = cv2.findContours(bw_image, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)[1]

    char_mask = np.zeros_like(img)
    bounding_boxes = []
    for contour in contours:
        x,y,w,h = cv2.boundingRect(contour)
        x,y,w,h = x-2, y-2, w+4, h+4
        bounding_boxes.append((x,y,w,h))


    characters = []
    for bbox in bounding_boxes:
        x,y,w,h = bbox
        char_image = img[y:y+h,x:x+w]
        characters.append(char_image)

    return characters

# ============================================================================    

def output_chars(chars, labels):
    for i, char in enumerate(chars):
        filename = "chars/%s.png" % labels[i]
        char = cv2.resize(char
            , None
            , fx=3
            , fy=3
            , interpolation=cv2.INTER_CUBIC)
        cv2.imwrite(filename, char)

# ============================================================================    

if not os.path.exists("chars"):
    os.makedirs("chars")

img_digits = cv2.imread("train_digits.png", 0)
img_letters = cv2.imread("train_letters.png", 0)

digits = extract_chars(img_digits)
letters = extract_chars(img_letters)

DIGITS = [0, 9, 8 ,7, 6, 5, 4, 3, 2, 1]
LETTERS = [chr(ord('A') + i) for i in range(25,-1,-1)]

output_chars(digits, DIGITS)
output_chars(letters, LETTERS)

# ============================================================================ 

The next step was to generate the training data from the character files we created with the previous script.

I followed the algorithm from the answer to the question mentioned above, resizing each character image to 10x10 and using all the pixels as keypoints.

I save the training data as char_samples.data and char_responses.data

Script to generate training data:

import cv2
import numpy as np

CHARS = [chr(ord('0') + i) for i in range(10)] + [chr(ord('A') + i) for i in range(26)]

# ============================================================================

def load_char_images():
    characters = {}
    for char in CHARS:
        char_img = cv2.imread("chars/%s.png" % char, 0)
        characters[char] = char_img
    return characters

# ============================================================================

characters = load_char_images()

samples =  np.empty((0,100))
for char in CHARS:
    char_img = characters[char]
    small_char = cv2.resize(char_img,(10,10))
    sample = small_char.reshape((1,100))
    samples = np.append(samples,sample,0)

responses = np.array([ord(c) for c in CHARS],np.float32)
responses = responses.reshape((responses.size,1))

np.savetxt('char_samples.data',samples)
np.savetxt('char_responses.data',responses)

# ============================================================================

Once we have the training data created, we can run the main script:

import cv2
import numpy as np

# ============================================================================

def reduce_colors(img, n):
    Z = img.reshape((-1,3))

    # convert to np.float32
    Z = np.float32(Z)

    # define criteria, number of clusters(K) and apply kmeans()
    criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 10, 1.0)
    K = n
    ret,label,center=cv2.kmeans(Z,K,None,criteria,10,cv2.KMEANS_RANDOM_CENTERS)

    # Now convert back into uint8, and make original image
    center = np.uint8(center)
    res = center[label.flatten()]
    res2 = res.reshape((img.shape))

    return res2 

# ============================================================================

def clean_image(img):
    gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

    resized_img = cv2.resize(gray_img
        , None
        , fx=5.0
        , fy=5.0
        , interpolation=cv2.INTER_CUBIC)

    resized_img = cv2.GaussianBlur(resized_img,(5,5),0)
    cv2.imwrite('licence_plate_large.png', resized_img)

    equalized_img = cv2.equalizeHist(resized_img)
    cv2.imwrite('licence_plate_equ.png', equalized_img)


    reduced = cv2.cvtColor(reduce_colors(cv2.cvtColor(equalized_img, cv2.COLOR_GRAY2BGR), 8), cv2.COLOR_BGR2GRAY)
    cv2.imwrite('licence_plate_red.png', reduced)


    ret, mask = cv2.threshold(reduced, 64, 255, cv2.THRESH_BINARY)
    cv2.imwrite('licence_plate_mask.png', mask) 

    kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
    mask = cv2.erode(mask, kernel, iterations = 1)
    cv2.imwrite('licence_plate_mask2.png', mask)

    return mask

# ============================================================================

def extract_characters(img):
    bw_image = cv2.bitwise_not(img)
    contours = cv2.findContours(bw_image, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)[1]

    char_mask = np.zeros_like(img)
    bounding_boxes = []
    for contour in contours:
        x,y,w,h = cv2.boundingRect(contour)
        area = w * h
        center = (x + w/2, y + h/2)
        if (area > 1000) and (area < 10000):
            x,y,w,h = x-4, y-4, w+8, h+8
            bounding_boxes.append((center, (x,y,w,h)))
            cv2.rectangle(char_mask,(x,y),(x+w,y+h),255,-1)

    cv2.imwrite('licence_plate_mask3.png', char_mask)

    clean = cv2.bitwise_not(cv2.bitwise_and(char_mask, char_mask, mask = bw_image))

    bounding_boxes = sorted(bounding_boxes, key=lambda item: item[0][0])  

    characters = []
    for center, bbox in bounding_boxes:
        x,y,w,h = bbox
        char_image = clean[y:y+h,x:x+w]
        characters.append((bbox, char_image))

    return clean, characters


def highlight_characters(img, chars):
    output_img = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
    for bbox, char_img in chars:
        x,y,w,h = bbox
        cv2.rectangle(output_img,(x,y),(x+w,y+h),255,1)

    return output_img

# ============================================================================    

img = cv2.imread("licence_plate.jpg")

img = clean_image(img)
clean_img, chars = extract_characters(img)

output_img = highlight_characters(clean_img, chars)
cv2.imwrite('licence_plate_out.png', output_img)


samples = np.loadtxt('char_samples.data',np.float32)
responses = np.loadtxt('char_responses.data',np.float32)
responses = responses.reshape((responses.size,1))


model = cv2.ml.KNearest_create()
model.train(samples, cv2.ml.ROW_SAMPLE, responses)

plate_chars = ""
for bbox, char_img in chars:
    small_img = cv2.resize(char_img,(10,10))
    small_img = small_img.reshape((1,100))
    small_img = np.float32(small_img)
    retval, results, neigh_resp, dists = model.findNearest(small_img, k = 1)
    plate_chars += str(chr((results[0][0])))

print("Licence plate: %s" % plate_chars)

Script Output

Enlarged 5x:

Enlarged image

Equalized:

Equalized image

Reduced to 8 colours:

Reduced colour space image

Thresholded:

Thresholded image

Eroded:

Eroded image

Mask selecting only characters:

Character mask

Clean image with bounding boxes:

Clean image with bounding boxes

Console output:

Licence plate: 2B99996


C++ code, using OpenCV 2.4.11 and Boost.Filesystem to iterate over files in a directory.

#include <boost/filesystem.hpp>

#include <opencv2/opencv.hpp>

#include <iostream>
#include <string>
// ============================================================================
namespace fs = boost::filesystem;
// ============================================================================
typedef std::vector<std::string> string_list;
struct char_match_t
{
    cv::Point2i position;
    cv::Mat image;
};
typedef std::vector<char_match_t> char_match_list;
// ----------------------------------------------------------------------------
string_list find_input_files(std::string const& dir)
{
    string_list result;

    fs::path dir_path(dir);

    fs::directory_iterator end_itr;
    for (fs::directory_iterator i(dir_path); i != end_itr; ++i) {
        if (!fs::is_regular_file(i->status())) continue;

        if (i->path().extension() == ".png") {
            result.push_back(i->path().string());
        }        
    }
    return result;
}
// ----------------------------------------------------------------------------
cv::Mat reduce_image(cv::Mat const& img, int K)
{
    int n = img.rows * img.cols;
    cv::Mat data = img.reshape(1, n);
    data.convertTo(data, CV_32F);

    std::vector<int> labels;
    cv::Mat1f colors;
    cv::kmeans(data, K, labels
        , cv::TermCriteria(CV_TERMCRIT_ITER | CV_TERMCRIT_EPS, 10000, 0.0001)
        , 5, cv::KMEANS_PP_CENTERS, colors);

    for (int i = 0; i < n; ++i) {
        data.at<float>(i, 0) = colors(labels[i], 0);
    }

    cv::Mat reduced = data.reshape(1, img.rows);
    reduced.convertTo(reduced, CV_8U);

    return reduced;
}
// ----------------------------------------------------------------------------
cv::Mat clean_image(cv::Mat const& img)
{
    cv::Mat resized_img;
    cv::resize(img, resized_img, cv::Size(), 5.0, 5.0, cv::INTER_CUBIC);

    cv::Mat equalized_img;
    cv::equalizeHist(resized_img, equalized_img);

    cv::Mat reduced_img(reduce_image(equalized_img, 8));

    cv::Mat mask;
    cv::threshold(reduced_img
        , mask
        , 64
        , 255
        , cv::THRESH_BINARY);

    cv::Mat kernel(cv::getStructuringElement(cv::MORPH_RECT, cv::Size(3, 3)));
    cv::erode(mask, mask, kernel, cv::Point(-1, -1), 1);

    return mask;
}
// ----------------------------------------------------------------------------
cv::Point2i center(cv::Rect const& bounding_box)
{
    return cv::Point2i(bounding_box.x + bounding_box.width / 2
        , bounding_box.y + bounding_box.height / 2);
}
// ----------------------------------------------------------------------------
char_match_list extract_characters(cv::Mat const& img)
{
    cv::Mat inverse_img;
    cv::bitwise_not(img, inverse_img);

    std::vector<std::vector<cv::Point>> contours;
    std::vector<cv::Vec4i> hierarchy;

    cv::findContours(inverse_img.clone(), contours, hierarchy, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE);

    char_match_list result;

    double const MIN_CONTOUR_AREA(1000.0);
    double const MAX_CONTOUR_AREA(6000.0);
    for (uint32_t i(0); i < contours.size(); ++i) {
        cv::Rect bounding_box(cv::boundingRect(contours[i]));
        int bb_area(bounding_box.area());
        if ((bb_area >= MIN_CONTOUR_AREA) && (bb_area <= MAX_CONTOUR_AREA)) {
            int PADDING(2);
            bounding_box.x -= PADDING;
            bounding_box.y -= PADDING;
            bounding_box.width += PADDING * 2;
            bounding_box.height += PADDING * 2;

            char_match_t match;
            match.position = center(bounding_box);
            match.image = img(bounding_box);
            result.push_back(match);
        }
    }

    std::sort(begin(result), end(result)
        , [](char_match_t const& a, char_match_t const& b) -> bool
        {
        return a.position.x < b.position.x;
        });

    return result;
}
// ----------------------------------------------------------------------------
std::pair<float, cv::Mat> train_character(char c, cv::Mat const& img)
{
    cv::Mat small_char;
    cv::resize(img, small_char, cv::Size(10, 10), 0, 0, cv::INTER_LINEAR);

    cv::Mat small_char_float;
    small_char.convertTo(small_char_float, CV_32FC1);

    cv::Mat small_char_linear(small_char_float.reshape(1, 1));

    return std::pair<float, cv::Mat>(
        static_cast<float>(c)
        , small_char_linear);
}
// ----------------------------------------------------------------------------
std::string process_image(cv::Mat const& img, cv::KNearest& knn)
{
    cv::Mat clean_img(clean_image(img));
    char_match_list characters(extract_characters(clean_img));

    std::string result;
    for (char_match_t const& match : characters) {
        cv::Mat small_char;
        cv::resize(match.image, small_char, cv::Size(10, 10), 0, 0, cv::INTER_LINEAR);

        cv::Mat small_char_float;
        small_char.convertTo(small_char_float, CV_32FC1);

        cv::Mat small_char_linear(small_char_float.reshape(1, 1));

        float p = knn.find_nearest(small_char_linear, 1);

        result.push_back(char(p));
    }

    return result;
}
// ============================================================================
int main()
{
    string_list train_files(find_input_files("./chars"));

    cv::Mat samples, responses;
    for (std::string const& file_name : train_files) {
        cv::Mat char_img(cv::imread(file_name, 0));
        std::pair<float, cv::Mat> tinfo(train_character(file_name[file_name.size() - 5], char_img));
        responses.push_back(tinfo.first);
        samples.push_back(tinfo.second);
    }

    cv::KNearest knn;
    knn.train(samples, responses);

    string_list input_files(find_input_files("./input"));

    for (std::string const& file_name : input_files) {
        cv::Mat plate_img(cv::imread(file_name, 0));
        std::string plate(process_image(plate_img, knn));

        std::cout << file_name << " : " << plate << "\n";
    }
}
// ============================================================================

C++ code, using OpenCV 3.1 and Boost.Filesystem to iterate over files in a directory.

#include <boost/filesystem.hpp>

#include <opencv2/opencv.hpp>

#include <iostream>
#include <string>
// ============================================================================
namespace fs = boost::filesystem;
// ============================================================================
typedef std::vector<std::string> string_list;
struct char_match_t
{
    cv::Point2i position;
    cv::Mat image;
};
typedef std::vector<char_match_t> char_match_list;
// ----------------------------------------------------------------------------
string_list find_input_files(std::string const& dir)
{
    string_list result;

    fs::path dir_path(dir);

    boost::filesystem::directory_iterator end_itr;
    for (boost::filesystem::directory_iterator i(dir_path); i != end_itr; ++i) {
        if (!boost::filesystem::is_regular_file(i->status())) continue;

        if (i->path().extension() == ".png") {
            result.push_back(i->path().string());
        }        
    }
    return result;
}
// ----------------------------------------------------------------------------
cv::Mat reduce_image(cv::Mat const& img, int K)
{
    int n = img.rows * img.cols;
    cv::Mat data = img.reshape(1, n);
    data.convertTo(data, CV_32F);

    std::vector<int> labels;
    cv::Mat1f colors;
    cv::kmeans(data, K, labels
        , cv::TermCriteria(CV_TERMCRIT_ITER | CV_TERMCRIT_EPS, 10000, 0.0001)
        , 5, cv::KMEANS_PP_CENTERS, colors);

    for (int i = 0; i < n; ++i) {
        data.at<float>(i, 0) = colors(labels[i], 0);
    }

    cv::Mat reduced = data.reshape(1, img.rows);
    reduced.convertTo(reduced, CV_8U);

    return reduced;
}
// ----------------------------------------------------------------------------
cv::Mat clean_image(cv::Mat const& img)
{
    cv::Mat resized_img;
    cv::resize(img, resized_img, cv::Size(), 5.0, 5.0, cv::INTER_CUBIC);

    cv::Mat equalized_img;
    cv::equalizeHist(resized_img, equalized_img);

    cv::Mat reduced_img(reduce_image(equalized_img, 8));

    cv::Mat mask;
    cv::threshold(reduced_img
        , mask
        , 64
        , 255
        , cv::THRESH_BINARY);

    cv::Mat kernel(cv::getStructuringElement(cv::MORPH_RECT, cv::Size(3, 3)));
    cv::erode(mask, mask, kernel, cv::Point(-1, -1), 1);

    return mask;
}
// ----------------------------------------------------------------------------
cv::Point2i center(cv::Rect const& bounding_box)
{
    return cv::Point2i(bounding_box.x + bounding_box.width / 2
        , bounding_box.y + bounding_box.height / 2);
}
// ----------------------------------------------------------------------------
char_match_list extract_characters(cv::Mat const& img)
{
    cv::Mat inverse_img;
    cv::bitwise_not(img, inverse_img);

    std::vector<std::vector<cv::Point>> contours;
    std::vector<cv::Vec4i> hierarchy;

    cv::findContours(inverse_img.clone(), contours, hierarchy, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE);

    char_match_list result;

    double const MIN_CONTOUR_AREA(1000.0);
    double const MAX_CONTOUR_AREA(6000.0);
    for (int i(0); i < contours.size(); ++i) {
        cv::Rect bounding_box(cv::boundingRect(contours[i]));
        int bb_area(bounding_box.area());
        if ((bb_area >= MIN_CONTOUR_AREA) && (bb_area <= MAX_CONTOUR_AREA)) {
            int PADDING(2);
            bounding_box.x -= PADDING;
            bounding_box.y -= PADDING;
            bounding_box.width += PADDING * 2;
            bounding_box.height += PADDING * 2;

            char_match_t match;
            match.position = center(bounding_box);
            match.image = img(bounding_box);
            result.push_back(match);
        }
    }

    std::sort(begin(result), end(result)
        , [](char_match_t const& a, char_match_t const& b) -> bool
        {
        return a.position.x < b.position.x;
        });

    return result;
}
// ----------------------------------------------------------------------------
std::pair<float, cv::Mat> train_character(char c, cv::Mat const& img)
{
    cv::Mat small_char;
    cv::resize(img, small_char, cv::Size(10, 10), 0, 0, cv::INTER_LINEAR);

    cv::Mat small_char_float;
    small_char.convertTo(small_char_float, CV_32FC1);

    cv::Mat small_char_linear(small_char_float.reshape(1, 1));

    return std::pair<float, cv::Mat>(
        static_cast<float>(c)
        , small_char_linear);
}
// ----------------------------------------------------------------------------
std::string process_image(cv::Mat const& img, cv::Ptr<cv::ml::KNearest> knn)
{
    cv::Mat clean_img(clean_image(img));
    char_match_list characters(extract_characters(clean_img));

    std::string result;
    for (char_match_t const& match : characters) {
        cv::Mat small_char;
        cv::resize(match.image, small_char, cv::Size(10, 10), 0, 0, cv::INTER_LINEAR);

        cv::Mat small_char_float;
        small_char.convertTo(small_char_float, CV_32FC1);

        cv::Mat small_char_linear(small_char_float.reshape(1, 1));

        cv::Mat tmp;
        float p = knn->findNearest(small_char_linear, 1, tmp);

        result.push_back(char(p));
    }

    return result;
}
// ============================================================================
int main()
{
    string_list train_files(find_input_files("./chars"));

    cv::Mat samples, responses;
    for (std::string const& file_name : train_files) {
        cv::Mat char_img(cv::imread(file_name, 0));
        std::pair<float, cv::Mat> tinfo(train_character(file_name[file_name.size() - 5], char_img));
        responses.push_back(tinfo.first);
        samples.push_back(tinfo.second);
    }

    cv::Ptr<cv::ml::KNearest> knn(cv::ml::KNearest::create());

    cv::Ptr<cv::ml::TrainData> training_data =
        cv::ml::TrainData::create(samples
            , cv::ml::SampleTypes::ROW_SAMPLE
            , responses);

    knn->train(training_data);

    string_list input_files(find_input_files("./input"));

    for (std::string const& file_name : input_files) {
        cv::Mat plate_img(cv::imread(file_name, 0));
        std::string plate(process_image(plate_img, knn));

        std::cout << file_name << " : " << plate << "\n";
    }
}
// ============================================================================

Community
  • 1
  • 1
Dan Mašek
  • 17,852
  • 6
  • 57
  • 85
  • 8
    That's some pretty bad-ass coding in such a short time period. Nice!! – Steger Apr 04 '16 at 20:29
  • 1
    Thank you. Yes, I tried. I put the characters on the boxes. When I had a clear picture, so I had six images, one for each character. But tesseract not recognize characters that maybe doesn't know the font of cz plates, so I do not know which approach would be appropriate to use. – Boidil Apr 04 '16 at 20:37
  • Thank you so much. Could you explain to me how you trained the characters in the picture? I trained localization of czech plates using the haar cascade, but I think the trained individual characters will be different. If you know. – Boidil Apr 04 '16 at 21:36
  • Here are a few of them. They shot from moving cars, so they are more than once. http://www.filedropper.com/cartar Cutting car plates not perfect, but it's ok. – Boidil Apr 04 '16 at 22:57
  • @DanMašek I have a problem, I tried to run the C ++ code with my cmake http://pastebin.com/s0JWu07z and boost library I installed, but the terminal writes: http://pastebin.com/tWtiSF5c. Do you know what is wrong? – Boidil Apr 05 '16 at 12:35
  • @DanMašek I do not understand. – Boidil Apr 05 '16 at 14:25
  • @DanMašek Thank you. It works. I still have a problem when the car plate has left a blue stripe, which has been identified as one character. http://postimg.org/image/l9urytuqp/ Do you know how to solve it? – Boidil Apr 05 '16 at 19:35
  • @DanMašek Thanks for the advice, I removed the blue part and it works. I still have a problem, how to recognize the difference between characters 8 a B. Because the plate 8B4 1473 is recognized as BB41473 and because every position can be every character, so it can't explicitly set. – Boidil Apr 05 '16 at 22:52
  • @DanMašek I would say that the same problem will be at 0 and O. – Boidil Apr 06 '16 at 18:13
  • @DanMašek Hello, I tried to recognize this car plate, but unsuccessfully. Do you know where is the problem?? Because it's the same plate but sensed from another position. http://postimg.org/image/vhnsk72yt/ Thank you for answer. – Boidil Apr 07 '16 at 15:40
  • @DanMašek Hello Dan. Do you know how to set up in recognition, to the first position was searched only numbers, letters to the second position and the rest of the numbers again? I can not figure it out. – Boidil Apr 11 '16 at 18:17
  • Maybe just create several instances of `KNearest` and train each of them with a different set of symbols (one with just numbers, one with just letters, one with everything, etc.). Then, as you iterate over the extracted character matches, pick the appropriate instance for each. – Dan Mašek Apr 11 '16 at 18:33
  • @DanMašek Can be added to each number or letter in training more source images? To increase the success of the resulting detection. And how? – Boidil Apr 12 '16 at 00:15
  • @Boidil I haven't tried it, but my guess is that it's possible. Just add more images with the same response. If you want, ping me on Skype and we can discuss this without abusing the comments here. That being said, maybe clean it up a bit before a moderator intervenes. – Dan Mašek Apr 12 '16 at 00:44
  • another axample of training and using KNN can be found [here](https://github.com/sturkmen72/OpenCV_3_KNN_Character_Recognition_Cpp) i tried to make some steps easier. – sturkmen Apr 28 '16 at 15:56