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

Да-да, вот так все просто выглядит. Это принтскрин с удаленного доступа к виртуальному серверу (да не сочтите это рекламой 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("<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("\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 (возможно, тоже из интернета).
Удачи
Вообще, алгоритм обучался на очень грязных зимних номерах (и парадоксально не всегда устойчиво работает на чистых), поэтому тут то его преимущества и стоит поискать. И да, действительно, часто размытые и весьма грязные номера удавалось распознать:



Ссылки:
Часть 1
Часть 2
Обновленные исходники Android-проекта
ссылка на оригинал статьи http://habrahabr.ru/post/223441/
Добавить комментарий