- PVSM.RU - https://www.pvsm.ru -
Неделю назад мы опубликовали статью [1] про открытый сервере для распознавания изображений автомобильных номеров. Теперь, как и обещали, статья про то, как отправлять на него свои фотографии с номерами. Наша цель была, как вы помните, вовсе не ругаться друг на друга неприличными словами, а именно сделать функционирующий сервер в интернете, который справляется с фотографиями и отправляет назад результат распознавания.

(часть фотографий, присланных в течение недели)
Хочется рассказать еще и о том, как мы — программисты, ворочающие нос от интернет технологий и Linux, — решали проблему с сервером.
Все мысли по поводу настоящего шумного компьютера под ухом, протягивание кабеля на кухню и переговоров с провайдером про реальный IP, были отброшены, как не соответствующие новым реалиям (со всех сторон только и говорят про облачные сервисы и прочие новинки). Но еще хотелось удобства, привычного Windows, dotNET, да и вообще возможности по-живому отлаживаться на сервере. Посему было решено: виртуальный сервер с Windows Server и удаленный рабочий стол.
Хочу передать огромное спасибо терпеливым и вежливым парням в техподдержке! Так что справились.

Да-да, вот так все просто выглядит. Это принтскрин с удаленного доступа к виртуальному серверу (да не сочтите это рекламой Windows Server 2012 R2).
Затем надо было написать http ответчик. Хотелось как можно проще и не связываться с IIS, нужно было уложиться в пару дней на разработку. Но оказалось очень просто скачать пример SimpleHttpServer [2] и в функцию:
public override void handlePOSTRequest(HttpProcessor p, StreamReader inputData) {
Console.WriteLine("POST request: {0}", p.http_url);
string data = inputData.ReadToEnd();
p.outputStream.WriteLine("<html><body><h1>test server</h1>");
p.outputStream.WriteLine("<a href=/test>return</a><p>");
p.outputStream.WriteLine("postbody: <pre>{0}</pre>", data);
}
вписывать нужную обработку. Надеюсь, мы не нарушили никакой лицензии.
А тем специалистам Web безопасности, у которых сейчас на спине зашевелились волосы от такой реализации… огромный привет и приглашение сделать нам все по умному!
Сервер распознавания работает, как очень простой http сайт. Пользователь отправляет на страницу post-сообщение в формате http, в котором содержится лишь один параметр — изображение. В ответ получает результат распознавания.
Для запроса из БД, если в этом есть необходимость, нужно отправить 2 строки: автомобильный номер в текстовом виде и уникальный ID.
В Android программе было 3 запроса, их код выглядит следующим образом:
1) отправка предварительно выделенного номера серверу:
HttpClient httpclient = new DefaultHttpClient();
final HttpParams httpParameters = httpclient.getParams();
HttpConnectionParams.setConnectionTimeout(httpParameters, 10 * 1000);
HttpConnectionParams.setSoTimeout (httpParameters, 10 * 1000);
//Создаём Http запрос и прилагаем к нему файл изображения
HttpPost httppost = new HttpPost("http://193.138.232.71:10000/result");
InputStreamEntity reqEntity;
httppost.setEntity(new FileEntity(new File(FileName), "application/octet-stream"));
//Получаем ответ от сервера
try {
HttpResponse response = httpclient.execute(httppost);
HttpEntity responseEntity = response.getEntity();
ans = EntityUtils.toString(responseEntity);
String[] strs=ans.split("rn");
if(strs.length>2)
{
ans=strs[0]; //Получаемый от сервера распознанный номер
timesWas=Integer.parseInt(strs[1]); //Сколько раз он встречался в базе
ID=strs[2]; //Унакальный ID текущей операции
}
} catch (ClientProtocolException e) {
e.printStackTrace();
ans = "NOT CONNECT";
} catch (IOException e) {
e.printStackTrace();
ans = "NOT CONNECT";
}
2) отправка запроса по номеру:
HttpClient httpclient = new DefaultHttpClient();
final HttpParams httpParameters = httpclient.getParams();
HttpConnectionParams.setConnectionTimeout(httpParameters, 10 * 1000);
HttpConnectionParams.setSoTimeout (httpParameters, 10 * 1000);
HttpPost httppost = new HttpPost("http://193.138.232.71:10000/checkplate");
InputStreamEntity reqEntity;
try {
httppost.setEntity(new StringEntity( editText1.getText().toString()+"rn"+ID));
HttpResponse resp = httpclient.execute(httppost);
HttpEntity ent = resp.getEntity();
String ans = EntityUtils.toString(ent);
timesWas=Integer.parseInt(ans);
textView.setText("Уже обозвали раз: "+Integer.toString(timesWas));
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}catch(Exception e)
{
e.printStackTrace();
}
3) «ругань» на номер:
HttpClient httpclient = new DefaultHttpClient();
final HttpParams httpParameters = httpclient.getParams();
HttpConnectionParams.setConnectionTimeout(httpParameters, 10 * 1000);
HttpConnectionParams.setSoTimeout (httpParameters, 10 * 1000);
HttpPost httppost = new HttpPost("http://193.138.232.71:10000/swear");
InputStreamEntity reqEntity;
try {
httppost.setEntity(new StringEntity( editText1.getText().toString()));
HttpResponse resp = httpclient.execute(httppost);
HttpEntity ent = resp.getEntity();
String ans = EntityUtils.toString(ent);
textView.setText("Обозван");
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}catch(Exception e)
{
e.printStackTrace();
}
По-моему, комментировать тут особенно нечего. HttpPost файла и HttpPost двух текстовых строк.
Не забывайте, что в условиях использования мобильного интернета, приходится отправлять область с предварительно обнаружнным номером с помощью каскадного детектора Хаара.
Пример кода выделения Хааром с помощью OpenCV на Android Java:
//Детектирование каскадом Хаара номера
if (mJavaDetector != null)
mJavaDetector.detectMultiScale(temp, faces, 1.1, 10, 5,
new Size(70, 21), new Size(500,150));
//Если нашлось
Rect[] facesArray = faces.toArray();
for (int i = 0; i < facesArray.length; i++)
{
DetectedNum = new Mat();
IsNumDetected=true;
//Новая рамка с чуть большими границами
int dW=facesArra[i].width/5; // расширяем рамку по X на 20%
int dH=facesArray[i].height*3/10; //по Y на 30%
int left = Math.max(facesArray[i].x-dW/2,0);
int top = Math.max(facesArray[i].y-dH/2,0);
int right = facesArray[i].x+facesArray[i].width+dW/2; if(right>temp.width())right=temp.width()-1;
int bottom = facesArray[i].y+facesArray[i].height+dH/2; if(bottom>temp.height())bottom=temp.height()-1;
//Отправка на сервер данного куска
DetectedNum = temp.submat(BiggerRect).clone();
}
Здесь заметьте важную мелочь: после детектирования прямоугольника номера его границы несколько расширяются, т. к. детектор с некоторой вероятностью может ошибаться с масштабом.
И по просьбе трудящихся добавили http заход на функцию поиска и распознавания номера в целом кадре: 193.138.232.71:10000/uploadimage
В ответ получите список найденных номеров и некий критерий качества распознавания по каждому (больше — лучше):
x000xx99 90%
a111aa197 75%
строки разделены "rn"
Найдено 2 номера, первый более качественный (90%), второй менее (75%).
Теперь можно не выделять хааром изображение, а сразу все изображение отправлять целиком. Так проще организовать автоматическое тестирование алгоритмов.
На других платформах код должен получаться не намного сложнее.
Программу на Android для ругани на автомобили Recognitor мы выложили 13 мая. У меня чувства смешанные: от гордости от того, что оно работает, до сжигающего стыда за случающиеся ошибки в алгоритме распознавания, когда прямо на глазах приходит чистый четкий номер, но пользователю возвращается абракадабра.
Количество отправленных на сервер изображений: 1700
Из них оказалось номерами РФ: 1370
Количество распознанных: 830
(с точностью до 10ти указано)
Вот тут стоит отдельно пояснить «из них оказалось номерами РФ». Мы не учли, что хабр хорошо читают на территории СНГ и нигде не указали, что номера должны быть РФ. Естественно, сюда же относятся и ошибки не идеально обученного каскадного детектора, который часто ошибался в непривычной ситуации съемки с монитора. И было несколько десятков зеркально отраженных номеров, т. е. пользователь не выбрал в меню “Flip”. Также ну очень сильно размазанные (не читаемые глазами) я тоже отнес сюда.
В промежуточном итоге результат не фантастический, мы сделали выводы, уже выпустили 2 обновления Android программы, поправив косяки и дав пользователю новую волшебную функцию выделения области номера пальцем. Изменили алгоритмы на сервере. О том, что интересного мы поменяли в самих алгоритмах, в моей следующей статье (воспользовались парой альтернативных методов из предыдущей моей статьи [3]).
Но, не смотря на не идеальную работу, пользователям приложение пришлось по душе! Оценки в GooglePlay радовали.
И да, конечно, поощрим бесспорных победителей:
P494KE_197 — обозван 226 раз (конечно, это ZlodeiBaal [4])
X777XX_77 – обозван 21 раз (в топе запроса яндекса на запрос «номера»)
Даже поймали A362MP_97, А231МР_97 и А869МР_97 (возможно, тоже из интернета).
Вообще, алгоритм обучался на очень грязных зимних номерах (и парадоксально не всегда устойчиво работает на чистых), поэтому тут то его преимущества и стоит поискать. И да, действительно, часто размытые и весьма грязные номера удавалось распознать:



Ссылки:
Часть 1 [3]
Часть 2 [1]
Обновленные исходники Android-проекта [5]
Update:
1) Оказывается, обученный каскад нами на российские автомобильные номера был замержин в основную версию OpenCV [6]
2) при предварительном выделении номера ожидали картинки довольно больших размеров в uploadimage, сейчас поправил, все приводится к одному масштабу. Должно заработать и на мелких картинках из интернета.
Автор: Vasyutka
Источник [7]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/open-source/60325
Ссылки в тексте:
[1] статью: http://habrahabr.ru/post/222539/
[2] SimpleHttpServer: http://www.codeproject.com/Articles/137979/Simple-HTTP-Server-in-C
[3] статьи: http://habrahabr.ru/post/221891/
[4] ZlodeiBaal: http://habrahabr.ru/users/zlodeibaal/
[5] Обновленные исходники Android-проекта: https://github.com/ZlodeiBaal/Recognitor
[6] основную версию OpenCV: https://github.com/Itseez/opencv/tree/master/data/haarcascades
[7] Источник: http://habrahabr.ru/post/223441/
Нажмите здесь для печати.