22/03/2011 11:19:58
Интересный и я бы даже сказал оригинальный способ автоматического считывания показаний газового счетчика предлагает постоянный пользователь сайта Tucker.
С целью получения в базу данных Умного Дома фактических показаний газового счетчика пришла идея сделать следующий комплекс. Так как стандартных способов или интерфейсов присоединения к обычному газовому счетчику не существует, было решено снимать показания с помощью фото-видео аппаратуры и распознавать показатели программой. В качестве аппаратуры рассматривались следующие варианты: цифровой фотоаппарат, веб-камера, аналоговая камера. В любом из перечисленных случаев управление устройством должно осуществляться с ПК. Также по понятным причинам необходимо предусмотреть постоянную освещенность счетчика. Как самый простой вариант, для тестирования данной идеи, была выбрана веб-камера со встроенной подсветкой за 320 руб (дешево, сердито и даже сурово). Была куплена вебкамера Defender G-Lens 321, которая к сожалению зависает через пару-тройку часов работы. Возможно дело в подсветке (во всяком случае без нее работает дольше - почти сутки, проблема пока не решена).
Настройка камеры: для оптимального распознавания лучше всего чтобы изображение было черно-белым, разрешение 640х480 (в результате получаем символы размером 20х40) - чем больше тем лучше, контрастность, яркость, sharpness настраиваются оптимально по ситуации. Съемка ведется фото-снимками, раз в минуту, снимки сохраняются в JPEG. Программы для Windows: Timershot (Powertoys XP) - не дает менять разрешение и делать настройки съемки, снимает в цвете; Freelabs Webcam Capture - небольшая программка, не плохой вариант; Willing Webcam Lite - громоздкая, мощная программа, отличные фильтры позволяющие получать четкую картинку, возможность вырезать кусок изображения при сохранении, множество функций типа детектора движения и т.д.
Алгоритм распознавания: для преобразования изображений в числа необходимо решить две задачи:
1) определение местоположения и границ цифр на снимке, которые могут меняться в случае перепозиционирования камеры и изменения показаний, а также удобное сохранение шаблонов из источника (функция обучения)
2) распознавание образов методом сравнения с шаблонами (аналог нейронной сети)
Программа распознает первые 5 цифр (кубометры) и 2 знака после запятой, 3-ий знак, который постоянно крутится - игнорируем. Возникают определенные сложности в случае не точного позиционирования цифр по оси, но эта проблема как-то решена.
Кол-во различающихся между собой шаблонов увеличивает шансы правильного распознавания, но также и замедляет скорость вычислений. Программу надо обучать под конкретные условия использования. В приведенном примере всего 6 шаблонов, что мало и подготовлены они с одного материала, в то время как изображения со счетчика делались разными программами и с разными настройками, что не правильно.

Ниже приводится основная часть скрипта на Perl, готовая к использованию, используются модули GD::Image и Image::Magick, готова для работы в Windows и Unix системах. Тестовые показания и шаблоны прилагаются.
#!/usr/bin/perl
# recognizer through templates v2.2 - min / Tucker 110321
# необходимые библиотеки:
use GD;
use Image::Magick; # используется только в модуле создания шаблонов
my $debug=0; # 1 - для режима отладки, выводятся все этапы распознавания, поток направить в файл
my $extract=0; # создание шаблонов, начиная с символа 1-7
my $image=$ARGV[0]; # изображение на обработку: JPEG
# для упрощения поиска символов, левый верхний угол изображения является началом табло счетчика (черное поле)
if ($image!~/.jpg$/i) { print "File format error"; exit; }
my $recresult=""; # сюда будет помещен результат распознавания
my $myImage = newFromJpeg GD::Image($image); # считываем изображение
my $wh_sens=80; # устанавливаем чувствительность белого, R>70 & G>70 & B>70 = White
# чем меньше значение, тем больше может быть захвачено мусора
# значение должно быть отторировано под конкретные условия освещения
# правильнее всего использовать с шаблонами созданными на той же чувствительности
my ($img_wdt,$img_hgt)=$myImage->getBounds; # определяем границы изображения, рекомендуется 640+
my $blk_hgt=50/320*$img_wdt; # начальная высота черного блока (50 пикс для разрешения 320)
my $blk_top=0; # верхняя граница черного блока, по умолчанию 0 (первая строка)
my $let_wdt=0; # ширина искомого, распознаваемого символа
my $let_hgt; # высота
my $symcount=0; # кол-во распознанных символов
my $sx,$sy; # координаты начала искомого символа (левый верхний угол)
for $x(0..$img_wdt) { # основной цикл, двигаемся по изображению слева на право X-координата
my @col; # содержание текущей колонки
for $y($blk_top..$blk_hgt) { # по блоку сверху вниз Y-координата, определяем черный-белый пиксел и запоминаем
$index = $myImage->getPixel($x,$y);
($r,$g,$b) = $myImage->rgb($index);
$white=0; $val=0;
if ( ( $r>$wh_sens )&&( $g>$wh_sens )&&( $b>$wh_sens ) ) { $white=1; $val=1; } # белый! вот чувствительность
if (!$x && $white) { $blk_hgt=$y-1; $blk_top=0; last; } # в первый проход по X корректируем высоту блока
push(@col,$val);
} # конец колонки
my $zzz=@col;
for ($i=($zzz-1);$i>=($zzz-15);$i--) { # бывает что снизу захватывает белый бордюр, если фото сделано под углом
if ($col[$i]) { pop(@col); } # подрезаем мусор
else { last; }
}
my $sum=0;
$sum += $_ for @col; # определяем кол-во пикселей в текущей колонке
if ($sum) { # если что-то есть, возможно это начало символа $sx, задача определить конец $x
if ($let_wdt==0) { $sx=$x; }
my $gr=join("",@col); $gr=~s/0/ /g; $gr=~s/1/X/g;
print "$x $sum : $gr :$let_wdt"
if ($debug); # выводим не пустые столбцы (цифры перевернуты)
$let_wdt++; # ширина символа
if (($x-$sx)>(13/320*$img_wdt)) { $sum=0; } # если текущая ширина символа превысила нормальную ширину, сбрасываем счетчик
}
if (!$sum && $let_wdt) { # пустая колонка, видимо конец символа
if ($let_wdt<(6/320*$img_wdt)) { goto SKIPTHIS; } # ширина недостаточная для символа, продолжаем поиск
# дальше определяем SY-верхнюю координату и EY-нижнюю координату символа
my $sy=0,$ey=0;
my $blk_mid=$blk_top+int(($blk_hgt-$blk_top)/2); # чтобы откинуть возможный мусор сверху-снизу, двигаемся от середины блока
my $midb,$blk_midb;
for $blk_midb($blk_mid..$blk_hgt) { # вверх
$val=0;
for $i($sx..($x-1)) {
$index = $myImage->getPixel( $i,$blk_midb );
( $r,$g,$b ) = $myImage->rgb( $index );
if ( ( $r>$wh_sens )&&( $g>$wh_sens )&&( $b>$wh_sens ) ) { $val++; }
}
$midb=$blk_midb;
if (!$val) { last; }
}
$ey=$midb;
my $midt;
for ($blk_midb=($blk_mid-1);$blk_midb>=$blk_top;$blk_midb--) { # и вниз
$val=0;
for $i($sx..($x-1)) {
$index = $myImage->getPixel( $i,$blk_midb );
( $r,$g,$b ) = $myImage->rgb( $index );
if ( ( $r>$wh_sens )&&( $g>$wh_sens )&&( $b>$wh_sens ) ) { $val++; } # и запоминаем считаем строки с белым
}
$midt=$blk_midb;
if (!$val) { last; }
}
$sy=$midt+1;
#### отладочный print - печатаем найденный символ
if ($debug) {
print "Symbol detected:";
for $j($sy..($ey-1)) {
for $i($sx..($x-1)) {
$index = $myImage->getPixel( $i,$j );
( $r,$g,$b ) = $myImage->rgb( $index );
if ( ( $r>$wh_sens )&&( $g>$wh_sens )&&( $b>$wh_sens ) ) {
print "X";
} else {
print "_";
}
}
print "";
}
}
#### eo print
$let_hgt=$ey-$sy; # высота символа
if ($let_hgt<(14/320*$img_wdt)) { goto SKIPTHIS; } # высота не достатончая, мусор или запятая, уходим
my $prop=$let_wdt/$let_hgt; # определяем пропорцию найденного символа, должно быть ~0,50
print "
X: $sx-$x Y: $sy-$ey Cols: $let_wdt Rows: ".$let_hgt." Symbol: $symcount" if $debug;
$symcount++;
# символ найден, производим опознание путем сравнения с заранее записанными шаблонами
# рассчитываем потенциал для каждой цифры, максимальный окажется искомым
my @ident_mas = (0,0,0,0,0,0,0,0,0,0); # массив потенциалов для цифр 0..9
my $num_index = 0;
for $dig(0..9) { # идем по порядку
$#template_pic=-1; # очистим массив, на всякий случай
for $ccc(10..20) { # кол-во шаблонов для каждой цифры (должно быть одинаковым)
$fnn="./digtemprec/t".$dig."_".$ccc.".png";
if (-e $fnn) { push(@template_pic,$fnn); }
}
foreach my $template ( @template_pic ) { # перебираем все шаблоны
my $tmp_image_bas = newFromPng GD::Image( $template ) || next; # можно читать и newFromJpeg
# в случае если шаблон другого размера, надо изменить размеры под распознаваемый символ
# а также определить какую часть шаблона задействовать (на случай если искомый символ на счетчике сдвинут - обрезан)
my $tmp_image=GD::Image->new($let_wdt,$let_hgt);
my ($tx,$ty)=$tmp_image_bas->getBounds;
$tsy=0; # позиция смещения в шаблоне, по умолчанию: берем целиком
$crp="";
if ($let_hgt<40/640*$img_wdt) { # судя по всему символ обрезан (мало высоты)
$tyc=int($ty*(0.5/$prop)); # определяем сколько обрезано, относительно нормы
if ($ey>($blk_hgt-16-20)/640*$img_wdt) { # обрезана нижняя или верхняя часть? 16 пикселей от низа блока до низа символов при разрешении 640, еще 20 это половина символа
$tsy=0; $ty=$tyc; $crp="*bot $prop";
}
else { # top cropped
$tsy=$ty-$tyc; $ty=$tyc; $crp="*top $prop";
}
}
$tmp_image->copyResized($tmp_image_bas,0,0,0,$tsy,$let_wdt,$let_hgt,$tx,$ty); # собственно шаблон готов
#$tmp_image->copyResampled #srcf,dstx,y,srcx,y,dstw,h,srcw,h # возможно на больших JPEG почувствуете разницу....
print "$let_hgt/$ey RRR: $tsy,$let_wdt,$let_hgt,$tx,$ty $crp" if ($debug);
# сравниваем найденный символ с шаблоном побитно
my $crcs=0;
for $j($sy..($ey-1)) {
my $bitl1="",$bitl2="";
for $i($sx..($x-1)) {
$index = $myImage->getPixel( $i,$j );
( $r1,$g1,$b1 ) = $myImage->rgb( $index );
if ( ( $r1>$wh_sens )&&( $g1>$wh_sens )&&( $b1>$wh_sens ) ) { $bitl1.='1'; } else { $bitl1.='0'; }
$pixel = $tmp_image->getPixel($i-$sx, $j-$sy);
($r2, $g2, $b2) = $tmp_image->rgb($pixel);
if ( ( $r2>$wh_sens )&&( $g2>$wh_sens )&&( $b2>$wh_sens ) ) { $bitl2.='1'; } else { $bitl2.='0'; }
}
$bitl1n=oct("0b1".$bitl1); $bitl2n=oct("0b0".$bitl2);
$xorr=$bitl1n ^ $bitl2n; # определяем не совпадения построчно и суммируем в $crcs
$xorrp=sprintf "%#bn",$xorr; $xorrp=~s/^0b1//;$xorrp=~s/n$//;
@sumx=split(//,$xorrp); my $tot=0; ($tot+=$_) for @sumx;
$crcs+=$tot;
print "$bitl1 $bitl2 : $xorrp $crcs" if $debug; # здесь наглядный вывод: что с чем сравнивается
}
$crce=1000000/(1+$crcs*$crcs); $ident_mas[$num_index]+=$crce; # расчитываем и сохраняем потенциал
print "----$template---($sx..$x)-($sy..$ey)--" if $debug;
} # каждый вариант шаблона текущей цифры
$num_index++;
} # каждая цифра
# определяем максимальный потенциал
my $max = $ident_mas[0];
my $max_id = 0;
for (my $k2 = 1; $k2 < 10; $k2++) {
if ($max < $ident_mas[$k2]) { $max = $ident_mas[$k2]; $max_id = $k2; }
}
print "================ Result symbol N$symcount: ".$max_id."" if $debug;
$recresult.=$max_id; # сохраняем найденное значение
if ($symcount==5) { $recresult.=","; }
#--- утилита создания шаблонов: найденный символ предлагается сохранить в папку шаблонов
# чем больше разных шаблонов для каждой цифры тем надежнее и медленнее распознавание
if ($extract && $symcount>=$extract) {
print "-- Extract: $symcount symbol, is it: $max_id ?";
for $j($sy..($ey-1)) {
my $bitl1="";
for $i($sx..($x-1)) {
$index = $myImage->getPixel( $i,$j );
( $r1,$g1,$b1 ) = $myImage->rgb( $index );
if ( ( $r1>$wh_sens )&&( $g1>$wh_sens )&&( $b1>$wh_sens ) ) { $bitl1.='1'; } else { $bitl1.='0'; }
}
print $bitl1."";
}
my $oImageMagick = Image::Magick->new;
$oImageMagick->Read($image);
$oImageMagick->Crop(x=>$sx, y=>$sy); # вырезаем с SX,SY по X,EY - наш символ
$oImageMagick->Crop(width=>$x, height=>$ey);
print "Save file? [0-9]: "; $fn=<STDIN>; chomp($fn);
if ($fn=~/d/) {
my $tmpf="./digtemprec/tmp.png";
$oImageMagick->Write($tmpf);
my @filedata=stat($tmpf); my $tmpfs=$filedata[7];
for (10..20) {
$fnn="./digtemprec/"."t".$fn."_".$_.".png";
if (-e $fnn) {
@filedata=stat($fnn); # [7]=filesize определяем размер файла и сохраняем только разные
if ($tmpfs==$filedata[7]) { print "Same size: $fnn ;not saved"; last; }
} else {
my $winf="$tmpf $fnn";
$winf=~s////g; # это для Windows
`copy $winf`; # это для Windows, поменять на cp для Unix
print "FILE SAVED: $fnn";
last;
}
}
}
}
#--- eo extract
SKIPTHIS: # сброс счетчиков
$let_wdt=0;
$sx=0; $sy=0;
if ($symcount==7) { last; }
}
}
print "RECOGNITION RESULT: ".$recresult."";
exit;
Полный архив с шаблонами содержит:
- скрипт .pl (для Linux закомментировать строку 213 и поменять в 214 'copy' на 'cp')
- файлы шаблонов (эталонов) для распознавания в директории digtemprec6
- файлы снимков счетчика в качестве примеров в директории recogn6ex
Наверное для обучения и распознавания образов можно было бы использовать библиотеку нейросети FANN, но поскольку я о ней тогда не знал, писал по-своему.
При занесении полученных значений в базу данных, в качестве дополнительного контроля правильности распознавания, имеет смысл убедиться в том, что каждое последующее значение больше предыдущего на величину равную среднему расходу газа за промежуток времени между снятиями показаний, ну и меньше следующего на ту же усредненную величину =)
Точных измерений! =)
Любое использование материалов сайта возможно только с разрешения автора и с обязательным указанием источника.
2011-09-02 10:16:02 | Виталий
Мне нужно снимать данные с цифрового дисплея электронных весов (как ЖКИ, так и на светодиодах).
Нужна программа.
За вознаграждение.
alehin_va@mail.ru
2011-06-05 02:15:52 | Владимир
И еще, если эту векамеру програмно выключать и включать, скажем через наждый час ан 5 минут (мне не так уж и сильно нужно показание счетчика совсем в риалтайм), она виснуть не будет?
2011-06-05 02:14:31 | Владимир
А как вы обходите ограничением usb (камера же web с usb) на максимальную длину usb кабеля в 5 метров? (или у вас счетчик находится недалеко от компьютера?)
2011-03-31 10:02:22 | Tucker
Запустил данный модуль под управлением Linux, как и предполагалось, никаких изменений не потребовалось, даже переобучение делать не стал, не смотря на то, что камера под линуксом выдает не совсем качественную мягко-говоря картинку. При том, что камера определилась системой сразу и появился /dev/video0, только в одной программе удалось получить хоть какое-то изображение, это vgrabbj -- как раз то, что нужно для этих целей. В плане зависания камеры -- проблема осталась, но в Linux все намного удобнее в плане диагностики проблем, все очень подробно записывается в логи, к тому же ядро автоматически переподсоединило устройство на /dev/video1, что в windows не возможно.
2011-03-23 10:35:36 | Andrey_B
Открыл тему на форуме
Предлагаю там продолжить обсуждение этой животрепещущей темы. ;)
2011-03-23 08:22:21 | THK
> Интересно, раз там внутри фактически просто геркон - нельзя ли сделать что-то свое?
Обычный геркон не срабатывает, пробовал 4 разных. В выходные попробую "фирменный" магнитный датчик от пневмоцилиндра (в оригинале реагирует на магнитную вставку в поршне, сквозь алюминиевую стенку цылиндра).
Можно еще попробовать датчик холла + операционник, но это фактически изобретение датчика о котором я писал.
> А второй вопрос - как на идущие от счетчика провода все-таки посмотрит контролирующая организация...
Установка датчика предусмотрена конструкцией счетчика, никаких изменений в конструкцию вносится фактически не будет. Я думаю, что их можно смело "посылать".
PS Не пора-ли организовать тему форума типа "Съём показаний счетчиков воды, газа, электричества"? А то обсуждение ушло в сторону от замечательной идеи...
2011-03-23 08:21:38 | Али
Андрей, не скажу за ваши органы, а за наши из Ленобласти могу точно сказать. Когда я поставил этот счетчик я специально вызвал газовщиков. Там если посмотрите на фотки справа от циферблата есть пластиковое ушко. Так вот оно позволяет намертво опечатать датчик к счетчику чтобы не было возможности самому его снять. Газовщики приехали, посмотрели и меня обматерили и сказали, чтобы с такой XXXX я к ним больше не обращался. Как я понял управляющая организация в поселке, доме может самостооятельно прицепить такой датчик без согласования, чтобы отслеживать потребление... Сам датчик идет с бумажкой, где указаны российские сертификаты позволяющие использовать данный девайс.
2011-03-23 00:19:37 | Tucker
Али, спасибо! Не давала покоя мне эта выемка)) Буду искать датчик. А вот как быть со счетчиком электроэнергии ЦЭ2727 ? Может тоже есть секрет?
2011-03-23 00:11:19 | Andrey_B
Вот есть вроде бы в природе датчик импульсов для моего Metrix G10. Называется он Metrix NI-3 (может быть, это тот же IN-Z61?), только вот где его взять - большой вопрос... Интересно, раз там внутри фактически просто геркон - нельзя ли сделать что-то свое? А второй вопрос - как на идущие от счетчика провода все-таки посмотрит контролирующая организация...
2011-03-22 22:03:54 | THK
Поспешил с коментарием... :( Вот, чтобы народ не искал...
Думаю на других счетчиках тоже самое.
Датчик импульсов IN-Z 61 (Z61) для счетчиков газа
Назначение:
Для использования в автоматизированных системах сбора информации счетчик газа может быть оснащен НЧ генератором импульсов IN-Z 6Х, состоящим из 2 язычковых контактов. Один из них срабатывает от магнитной вставки, встроенной в ролик младшего разряда отсчетного устройства. Второй контакт предназначен для сигнализации влияния на контакт внешним магнитным полем.
Описание:
НЧ генератор импульсов выпускается в трех исполнениях:
IN-Z 61 – с разъединяющим кабелем
IN-Z 62 – с зажимами для подключения кабеля
IN-Z 63 – с выходным коннектором
Технические характеристики датчика-генератора импульсов IN-Z 61 (Z61):
- Количество импульсов 100 имп./м3
- Максимальное напряжение 24 В
- Максимальная сила тока 50 мА
- Минимальное количество замыканий 7х107 в мин.
- Максимальная мощность 0,25 Вт
- Минимальное время импульса 0,25 с
- Максимальное сопротивление при замыкании 0,5 Ом
2011-03-22 21:57:14 | THK
У меня BK-G4...
Надо искать датчик.
Али, Вас не затруднит проверить на что реагирует датчик (на металл или на магнит)?
Может и искать ничего не надо?!
2011-03-22 16:57:37 | Andrey_B
Али, главное ведь идея. Ведь аналогичный способ можно применить и в других ситуациях ;)
А вообще вы сказали интересную вещь. У меня вот используется польский газовый счетчик Metrix G10. У него тоже есть под дисплеем выемка. Никогда не задумывался для чего она... Ну, теперь дело чести прицепить его к 1-wire ;) Спасибо, что направили на путь истинный. ;)
2011-03-22 16:40:05 | Али
Воистину слава задору энтузиастов! Особенно когда надо почесать левое ухо правой рукой :) На фотографиях статьи изображен счетчик газа BK-G4 от уважаемой компании Эльстер. Если посмотреть на первую фотку то мы увидим под дисплеем выемку. И она там не просто так!! А она там для датчика импульсов под названием IN-Z61. Выглядит он вот так /www.elstermetering.co.uk/en/2306.html Вещь стандартная, сертифицированная, установка согласований не требует. У производителя в Н.Новгороде стоит порядка 800 рублей. У перекупщиков по городам стоит от 1500 до 2100 руб в зависимости от жадности. Мне доставили от со склада производителя за 1800 руб за 5 дней. Гораздо красивее повесить на этот генератор импульсов 1-wire счетчик стандартный и компьютером его считывать. Если брать счетчик от HobbyBoard, то он с батарейкой. То есть подсчет идет даже когда нет подачи электричества.