ФОРУМ КУПИТЬ

Последние статьи

ВСЕ СТАТЬИ

Снятие показаний газового счетчика с помощью Web-камеры

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

Скачать recogn.zip

Наверное для обучения и распознавания образов можно было бы использовать библиотеку нейросети FANN, но поскольку я о ней тогда не знал, писал по-своему.

При занесении полученных значений в базу данных, в качестве дополнительного контроля правильности распознавания, имеет смысл убедиться в том, что каждое последующее значение больше предыдущего на величину равную среднему расходу газа за промежуток времени между снятиями показаний, ну и меньше следующего на ту же усредненную величину =)

Точных измерений! =)

Любое использование материалов сайта возможно только с разрешения автора и с обязательным указанием источника.



Добавить комментарий:

(необязательно, не отображается на сайте)


Сортировка комментариев: Последние сверху | Первые сверху

2014-01-11 21:40:14 | Andrey_B
Шерхан, на нашем форуме достаточно часто обсуждается применение Raspberry Pi и других мини-ПК в качестве сервера Умного Дома. Думаю, в ряде случаев это вполне оправдано и возможно. А вот Arduino применять в качестве сервера я бы не стал. Очень ограниченные ресурсы, не позволяющие использовать на этой платформе полноценную ОС и высокоуровневые языки программирования. А вот в качестве интеллектуальных исполнителей микроконтроллеры в Умном Доме хороши. Смотрите проект MegaD-328.


2014-01-10 07:34:14 | Шерхан
Вопрос а что если Росбери использовать как комп? или же ардуино с GSM модулем ?


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, то он с батарейкой. То есть подсчет идет даже когда нет подачи электричества.