Распознавание номеров: от А до 9. Часть 3

Неделю назад мы опубликовали статью про открытый сервере для распознавания изображений автомобильных номеров. Теперь, как и обещали, статья про то, как отправлять на него свои фотографии с номерами. Наша цель была, как вы помните, вовсе не ругаться друг на друга неприличными словами, а именно сделать функционирующий сервер в интернете, который справляется с фотографиями и отправляет назад результат распознавания.880e12d1f3f1f697dae88a5ba59c9503.jpg(часть фотографий, присланных в течении недели)

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

55d2a14388385da6662e74bc353a395f.jpgДа-да, вот так все просто выглядит. Это принтскрин с удаленного доступа к виртуальному серверу (да не сочтите это рекламой Windows Server 2012 R2).

Затем надо было написать http ответчик. Хотелось как можно проще и не связываться с IIS, нужно было уложиться в пару дней на разработку. Но оказалось очень просто скачать пример SimpleHttpServer и в функцию:

public override void handlePOSTRequest (HttpProcessor p, StreamReader inputData) { Console.WriteLine («POST request: {0}», p.http_url); string data = inputData.ReadToEnd (); p.outputStream.WriteLine (»

test server

»); p.outputStream.WriteLine (»return

»); p.outputStream.WriteLine («postbody:

{0}
», 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 (»\r\n»); 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 ()+»\r\n»+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%строки разделены »\r\n«Найдено 2 номера, первый более качественный (90%), второй менее (75%).

Теперь можно не выделять хааром изображение, а сразу все изображение отправлять целиком. Так проще организовать автоматическое тестирование алгоритмов.

На других платформах код должен получаться не намного сложнее.

Несколько слов о трех днях полета сервера распознавания номеров Программу на Android для ругани на автомобили Recognitor мы выложили 13 мая. У меня чувства смешанные: от гордости от того, что оно работает, до сжигающего стыда за случающиеся ошибки в алгоритме распознавания, когда прямо на глазах приходит чистый четкий номер, но пользователю возвращается абракадабра.Количество отправленных на сервер изображений: 1700Из них оказалось номерами РФ: 1370Количество распознанных: 830(с точностью до 10ти указано)

Вот тут стоит отдельно пояснить «из них оказалось номерами РФ». Мы не учли, что хабр хорошо читают на территории СНГ и нигде не указали, что номера должны быть РФ. Естественно, сюда же относятся и ошибки не идеально обученного каскадного детектора, который часто ошибался в непривычной ситуации съемки с монитора. И было несколько десятков зеркально отраженных номеров, т. е. пользователь не выбрал в меню «Flip». Также ну очень сильно размазанные (не читаемые глазами) я тоже отнес сюда.В промежуточном итоге результат не фантастический, мы сделали выводы, уже выпустили 2 обновления Android программы, поправив косяки и дав пользователю новую волшебную функцию выделения области номера пальцем. Изменили алгоритмы на сервере. О том, что интересного мы поменяли в самих алгоритмах, в моей следующей статье (воспользовались парой альтернативных методов из предыдущей моей статьи).Но, не смотря на не идеальную работу, пользователям приложение пришлось по душе! Оценки в GooglePlay радовали.

И да, конечно, поощрим бесспорных победителей: P494KE_197 — обозван 226 раз (конечно, это ZlodeiBaal)X777XX_77 — обозван 21 раз (в топе запроса яндекса на запрос «номера»)Даже поймали A362MP_97, А231МР_97 и А869МР_97 (возможно, тоже из интернета).

Удачи Вообще, алгоритм обучался на очень грязных зимних номерах (и парадоксально не всегда устойчиво работает на чистых), поэтому тут то его преимущества и стоит поискать. И да, действительно, часто размытые и весьма грязные номера удавалось распознать: 39313d41bb1182ec9f4aad539a983cfa.jpgf9f207dc5628569fadebcf7677d4b211.jpg7ae4ca89cd9b8e247163f04501516ead.jpgСсылки: Часть 1Часть 2Обновленные исходники Android-проекта

© Habrahabr.ru