Pull to refresh
65.19
Recognitor
Computer Vision and Machine Learning

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

Reading time 6 min
Views 80K
Неделю назад мы опубликовали статью про открытый сервер для распознавания изображений автомобильных номеров. Теперь, как и обещали, статья про то, как отправлять на него свои фотографии с номерами. Наша цель была, как вы помните, вовсе не ругаться друг на друга неприличными словами, а именно сделать функционирующий сервер в интернете, который справляется с фотографиями и отправляет назад результат распознавания.


(часть фотографий, присланных в течение недели)


Хочется рассказать еще и о том, как мы — программисты, ворочающие нос от интернет технологий и 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://212.116.121.70/: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://212.116.121.70: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://212.116.121.70: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 заход на функцию поиска и распознавания номера в целом кадре: 212.116.121.70: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-проекта

Update:
1) Оказывается, обученный каскад нами на российские автомобильные номера был замержин в основную версию OpenCV
2) при предварительном выделении номера ожидали картинки довольно больших размеров в uploadimage, сейчас поправил, все приводится к одному масштабу. Должно заработать и на мелких картинках из интернета.
Tags:
Hubs:
+56
Comments 29
Comments Comments 29

Articles

Information

Website
cvml.ru
Registered
Employees
2–10 employees
Location
Россия