Ви не увійшли.
Учитывая актуальную задачу «экономии» ресурсов возникла идея считывания показаний счетчика с возможностью в дальнейшем проведения анализа и построения графиков потребелния. Работу электросчетчика можно контролировать с помощью измерителя мощности, например, такого как PZEM-004. Для счетчика воды можно врезать в водопровод дополнительный водомер. Для учета потребления газа это сделать самостоятельно невозможно - поэтому и решено было попробовать создать устройство считывания показаний. Поиск в интернете показал, что задача выполнима, хотя и довольно сложна в виду отсутствия у меня соответствующих в том числе и базовых знаний.
Как правило в квартирах установлены счетчика старого образца, которое не имеют устройства встроенного модуля считывания данных и не формирует импульсов, из-за этого единственный способ снятия показаний — оптический.
На первых этапах проработки идеи рассматривался вариант считывания показаний только последней цифры счетчика с помощью фото и светодиода (датчик TCRT5000), например, подсчет белых делений (https://www.ab-log.ru/forum/viewtopic.php?t=8&start=160). На эту идею натолкнула также разработка, в которой предложено считывать с помощью фотоэлемента импульсы светодиода электросчетчика (https://habr.com/ru/post/234679/, https://forum.arduino.ua/viewtopic.php?id=857, https://mysensors.ru/build/pulse_water/). Однако данный метод предполагает установку первоначальных значений и не обеспечивает возврата к текущим показаниям в случае сбоя.
В результате было принято решение построить систему способную распознать цифры на шкале счетчика. Как известно, сейчас доступен, в том числе и на бесплатной основе, сервис по распознаванию текста на картинках в интернете. Естественно был рассмотрен вариант передачи файла с изображением счетчика и получением результата распознавания. На https://www.youtube.com/watch?v=Upcr_rXnMQY было выложено видео в котором тестировалось Google Vision. В ходе экспериментов несколько фотографий шкалы без предварительной обработки были «скормлены» таким бесплатным сайтам. В большинстве случаем либо они отказывались распознавать цифры и сообщали, что это капча или в виду плохого качества фотографии распознавали их неправильно. Причем текст на лицевой панели самом счетчика распознавался лучше чем цифры шкалы. Можно было бы установить на компьютере одну из бесплатных программ распознавания, однако, как мне показалось такое решение выглядит несколько громоздким. Кроме того в любом случае требовалась предварительная обработка фотография.
В качестве основного модуля использован ESP32-CAM. Данный модуль обладает достаточными ресурсами, компактен и имеет встроенную камеру. Для вывода служебной и визуальной информации был вначале использован TFT дисплея 1,8“ работающий по шине SPI. В окончательном варианте был применен 2,0“ TFT дисплей с разрешающей способностью 220х176 пикселей. Данный дисплей позволил вывести больше необходимой визуальной информации и показать всю шкалу счетчика практически полностью. Все используемые компоненты могут питаться от 5 В, но при этом хорошо стыкуются по 3,3 В.
Вначале все делалось на «коленках» в виде конструктора из досточек и изоленты. Любое неосторожное задевание стола или неудачный жест рукой приводил к необходимости по новой строить макет. В качестве дисплея был использован счетный механизм от старого счетчика, который в свое время не прошел поверку.
Когда надоело мучатся и подошло время натуральных живых испытаний для удобства отладки устройства камера с процессором и дисплей были собраны на единой плате. Возможно из-за того, что встроенная антенна ESP32-CAM была обращена в строну платы связь по WiFi была неустойчивая. Пришлось выпаивать модуль с процессором/камерой и подключать внешнюю антенну. Из аппаратной части также был установлен преобразователь USB-TTL для связи с компьютером и возможностью прошивки модуля. Повышающий преобразователь МТ3806 был использован для формирования напряжения питания светодиодов подсветки в пределах 7-9 В. Включение модулей стандартное и не имеет каких либо особенностей. В библиотеки доработки не вносились.
Более подробно об алгоритме обработки изображения:
Камера ESP32-CAM работает в режиме градаций серого с разрешением 320х240 пикселей. Каждый пиксель имеет значение от 0 до 255 и занимает один байт.
Из-за того, что фокусное расстояние штатного объектива линзы камеры около одного метра перед камерой установлена дополнительная линза 5Х (недорогая линза найденная в ближайшем магазине). Это позволило получить боле-менее не замыленное изображение шкалы счетчика на расстоянии около 10 сантиметров при захвате всей шкалы сразу. Установка линзы с большим увеличением привело бы к тому, что расстояние между камерой и счетчиком сократилось, но вся шкала тогда не помещалась. Эксперименты показали, что подходит любая линза от 4Х до 8Х.
Для устранения шумов камеры полученные изображения (кадры) в градациях серого усредняются (sum_frames) и интегрированный результат помещается в отдельный буфер с разрешением 320х240 пикселей (camera_capture). С помощью Web интерфейса число кадров суммирования можно выбрать от 1 до 10. По умолчанию установлено 5 кадров.
В функции find_middle_level_image предварительно автоматически определяется уровень яркости изображения методом Отцу. Данный метод дает более стабильный результат по сравнению со среднеарифметическим и наиболее точно приближен к «идеальному» уровню бинаризации, который обеспечивает более качественное выделение цифр шкалы.
Процедура find_digits_y позволяет определить границы расположения строки с изображением шкалы счетчика в общем изображении получаемым от камеры. При проведении бинаризации, для определенного на предварительном шаге уровня яркости, строчки изображения которые соответствуют шкале счетчика будут иметь максимальное суммарное значение пикселей, т. к. в этом месте общего изображения будет самое большое количество засвеченных пикселей. Определенные на этом этапе границы отображаются на индикаторе в виде трех горизонтальных линий (верх, середина и низ цифр) для визуального анализа и последующей коррекции при необходимости.
Для того, чтобы хота как-то можно было избавиться от посторонних засветов линзу были установлены «шторки». В дальнейшем учитывая то, что камера и индикатор расположены неподвижно друг по отношению к другу и после первоначальной установки не перемещаются - на последующих этапах можно применить так называемый программный метод «шторок», с помощью которого ограничивалась область индикатора с точностью до нескольких пикселей и тем самым обрезать засветка его границ. Дополнительную корректировку значений границ цифр шкалы счетчика и метоположения самого индикатора, также можно выполнить через Web интерфейс.
В процедуре find_max_letter_X осуществляется поиск середины цифр. Уровень бинаризации повышается и по оси Х строится гистограммы суммы пикселей по вертикали. Дополнительно не учитываются еще два пикселя младших пикселя. После нахождения границ первой цифры осуществляется поиск следующей с шагом равным ширине цифры - 26 пикселей. Для каждой найденной цифры строятся вертикальные линии: левый край, середина, правый край показывающие точность автоматического нахождения положения цифры на изображении. Варианты построения гистограмм яркостных уровней по осям Х и Y для каждой цифры показаны на фотографиях.
На дисплей также вывена информация об уровне середины цифр, который на показанных фотографиях равен 45 пикселям (Ymid), высота цифр 21 пикселm (Y_d) и средний уровень яркости (mid_lev) изображения 39 единиц. В дальнейшем после отладки данного шага алгоритма от вывода гистограмм на экран дисплея я отказался. В связи стем, что горизонт камеры и счетчика выставлены точно и не перемещаются в дальнейшем уточнении границ расположения шкалы счетчика нет необходимости. Поэтому гистограмма по оси Y для каждой цифры не используется в алгоритме распознавания. Следует обратить внимание, что если при первоначальном нахождении положения окна шкалы счетчика по оси Y пороговая яркость для бинаризации не изменялась по сравнению с определенной по всему изображению, то для определения границ отдельных цифр по оси Х уровень яркости был повышен на 60 единиц. Если этого не делать, то не произойдет четкого разделения яркостной гистограммы на отдельные цифры. Т.е. на каждом из выше приведенных этапов обработки был применен свой уровень яркости при проведении бинаризации.
Качество получаемого от камеры изображения в том числе зависит и от освещенности. К сожалению, даже применение отельных линеек светодиодов для подсветки шкалы счетчика и закрытие всей конструкции от внешнего света не позволяет добиться идеальной и равномерной яркости для всех цифр одновременно. Поэтому на следующем этапе определив места расположения середины цифры по оси Х в пределах предполагаемой ширины цифры в границах расположения цифр по оси Y производиться расчет среднего уровня яркости для каждой отдельной цифры шкалы счетчика (процедура find_middle_britnes_digital).
Для поиска совпадения полученного изображения цифр с эталоном в начале был опробован метод свертки https://ru.m.wikipedia.org/wiki/Свёртка … й_анализ)) . Однако этот метод не давал приемлемого результата и предпочтение было отдано подсчету расстояния Хемминга https://ru.wikipedia.org/wiki//Расстояние_Хэмминга.
Для удобства расчета расстояния Хемминга при наложении эталона на изображение осуществляется бинаризация и преобразование изображения в 32 битное число в процедуре convert_to_32. Уровень бинаризации для каждой отдельной цифры выбирается в этом случае индивидуальный (процедура find_middle_britnes_digital).
Расчет и поиск минимального расстояния Хемминга выполняется в функции image_recognition, которая возвращает значение распознанной цифры. При изменении значения счетчика в процессе своей работы цифры могут занять положение, которое смещено по отношению к середине шкалы счетчика по высоте (промежуточные положения цифр не учитываются). Кроме того, при определении границ каждой цифры возможны отклонения на несколько пикселей как по оси X так и по Y. Поэтому, сравнение эталона с распознаваемой цифрой производиться с учетом возможного смещения на 4 пикселя вверх, 4 пикселя вниз, 1 пиксель право и 1 пиксель влево. Всего возможно 27 вариантов различных смещений. Из всех рассчитанных вариантов сдвигов и всех имеющихся эталонов выбирается тот, который имеет меньшее значение расстояния Хемминга. В качестве порогового, в дальнейшем, применяется значение расстояния Хемминга менее 50 единиц.
Для последующего усреднения и накопления результатов распознавания цифр в процедуре frequency происходит подсчет числа совпадения распознанной цифры. По результатам выбирается цифра имеющая максимальное значение. Всего таким образом учитывается 10 выборок. В качестве порогового применяется значение равное 7. Таким образом, если из 10 выборок не менее 7 раз распознана одна и та же цифра и при этом расстояние Хеминга было менее порогового уровня 60 — считается, что цифра распознана правильно и она выводиться на индикатор желтым цветом. Если данные условия не выполняются, то цифра имеет красный цвет. В случае, когда на предыдущем цикле распознавания цифра уже имела такое же значение, то она становиться зеленой.
Для визуального контроля на дисплей может выводятся изображение шкалы в следующих режимах:
- черно-белое изображение, которое прошло бинаризацию согласно описанного выше алгоритма. Данный режим позволяет подкорректировать уровень бинаризации и определить засветы.
- шкала выведенная в градациях серого. Распознавание при этом осуществляется также согласно алгоритма. Применяется для визуального считывания данных шкалы и более точного нахождения границ цифр;
- изображение выводиться в градациях серого на весь экран. Распознавание не осуществляется. Данный режим необходим при первоначальных установках положения камеры и позволяет определить где находиться шкала на изображении.
На 1,8“ дисплее одновременно помещается первых 5 цифр счетчика, которое собственно и нужны при передаче показаний (остальные три цифры после запятой). На 2,0“ помещается все цифры. Через Web интерфейс при необходимости можно осуществить сдвиг выводимой информации для визуального просмотра и анализа.
На первых этапах разработки программы в качестве эталонного использовался шрифт получаемый от программного знакогенератора. Однако, как выяснилось позже каждый счетчик может иметь свой шрифт на табло. Кроме того, формируемый шрифт знакогенератором имеет фиксированные размеры 22, 24 и т.д. пикселя по высоте, что не всегда удобно. Кроме того трудно подобрать шрифт, которые будет максимально совпадать со шрифтом шкалы счетчика.
В данной реализации высота цифр принята 26 пикселя. В качестве эталона использовано полученное от камеры бинаризированное изображение. Для упрощения этой процедуры в программе предусмотрена возможность вывода в порт в шестнадцатеричном формате цифр шкалы. Данные могут быть скопированы сразу в программу для последующего использования как эталонные. Эталонны хранятся в виде массива uint32_t sample[number_of_samples][sample_height] файл sample.h. Массив эталонов может быть расширен различными версиями начертания цифр в том числе и занимающие половинное положение.
Некоторые этапы разработки приведены на фото ниже
На последней фотографии видно, что даже самая последняя цифра успела определиться корректно. На этой же фотографии в нижней строчке выведены значения индивидуальной яркости для каждой цифры (минимальное 62 максимальное 103).
Оперативное управление осуществляется через Web (файл myserver.h), интерфейс которого показан на рисунке и имеет следующие органы управления:
number_of_sum_frames — число кадров суммирования изображения;
offset_y — смещение по оси Y при выводе на дисплей и интегрировании кадров. Позволяет подогнать высоту расположения индикатора разных моделей;
level_dispalay_ttf_B_W — дополнительный уровень яркости добавляемый к автоматически определенному при выводе на дисплей в черно-белом режиме;
level_find_digits_y — дополнительный уровень яркости добавляемый к автоматически определенному при нахождении границ цифр по высоте — ось Y;
level_find_max_letter_X — дополнительный уровень яркости добавляемый к автоматически определенному при нахождении границ цифр по ширене — ось Х;
level_convert_to_32 — дополнительный уровень яркости добавляемый к автоматически определенному при бинаризации изображения;
level_Y_up — уровень программной «шторки» выше которой изображение ограничивается — позволяет убрать засветы на индикаторе;
level_Y_down — уровень программной «шторки» ниже которой изображение ограничивается — позволяет убрать засветы на индикаторе;
show_digital — выводит на экран монитора процесс результатов расчета расстояния Хеминга в виде двоичного представления выбранной цифры и эталонов с учетом всех сдвигов. Занимает много времени;
offset_x — смещение по оси Х для вывода на дисплей цифр, которое не помещаются на экране дисплея (6-8).
Gray or BW? - позволяет выбрать режим индикации изображения шкалы в режиме черно-белом, шкала в режиме градаций серого, все изображение в режиме градаций серого, которое помещается на экран;
Show 0/1? - выводит на экран монитора цифры после бинаризации в виде 1 и пробелов или 1 и 0 для возможности визуального анализа работы программы;
Show HEX? - выводит на экран монитора цифры после бинаризации в шестнадцатеричном виде для использования их в качестве эталонна;
Found digitals : - результаты текущего распознавания и долгосрочного распознавания. Значение 10 показывает, что цифра ни разу не была определена корректно по указанным выше критериям
Сам интерфейс написан некорректно и не оптимально, но позволяет оперативно осуществлять предварительную настройку программы.
Из-за перехода на 2,0“ дисплей пришлось полностью переписать программу под другую библиотеку TFT_22_ILI9225, т. к. в нем используется контроллер ILI9225, а в версии 1,8“ ST7735.
После «жесткой» сборки всей конструкции, несмотря на то, что алгоритм поиска положения границ цифр по высоте (ось Y), работал исправно, применение программных шторок сразу уменьшает зону поиска до нужных границ. Кроме того, так как фокусное расстояние не изменяется размер цифр также не может поменяться. В процедуре find_digits_y фактически находится середина цифр, а границы определяются как ½ высоты цифр, т. е. +/- 13 пикселей.
Для накопления результатов объема потребленного газа м3 в программе реализован кольцевой буфер размерностью 2048 процедура m3_calculate(). Показания сохраняются каждую минуту. Если изменений в показаниях не произошло или нет правильного результата происходит увеличение текущего времени простоя. Так как буфер кольцевой, то после его заполнения запись начинается сначала. Выводом накопленных результатов занимается процедура print_m3(). При выводе значений из текущего времени получаемого от NMT сервера отнимаются накопленные минуты, что позволяет узнать время начала записи. Результаты выводятся в виде таблицы порядковый номер, значение m3, время простоя в минутах. За сутки заполняется приблизительно 70-80 ячеек кольцевого буфера. Для корректного сравнения float все значения показаний счетчика были умножены на 100 и тем самым переведены в целое.
Оригинальным решением подсказанным мне стало фиксация конструкции вот такими креплениями за трубы счетчика. В качестве корпус решил применить пластиковый пищевой контейнер.
Итоговый вариант
Интерфейс управления был переписан под библиотеку Virtuino
Управление аналогично выше описанному Web интерфейсу:
режим дисплея - позволяет выбрать режим индикации изображения шкалы в режиме черно-белом, шкала в режиме градаций серого, все изображение в режиме градаций серого, которое помещается на экран;
сдвиг для анализа
- смещение по оси Х при выводе на дисплей и интегрировании кадров.
Позволяет подогнать расположения для индикаторов разных моделей по горизонтали;
- смещение по оси Y при выводе на дисплей и интегрировании кадров.
Позволяет подогнать высоту расположения индикатора разных моделей;
дисплей — дополнительный уровень яркости добавляемый к автоматически определенному при выводе на дисплей в черно-белом режиме;
яркость Y — дополнительный уровень яркости добавляемый к автоматически определенному при нахождении границ цифр по высоте — ось Y;
яркость X — дополнительный уровень яркости добавляемый к автоматически определенному при нахождении границ цифр по ширене — ось Х;
conv 32 — дополнительный уровень яркости добавляемый к автоматически определенному при бинаризации изображения;
Y_up — уровень программной «шторки» выше которой изображение ограничивается — позволяет убрать засветы на индикаторе;
Y_down — уровень программной «шторки» ниже которой изображение ограничивается — позволяет убрать засветы на индикаторе;
сдвиг для просмотра на дисплее — смещение по оси Х для вывода на дисплей цифр, которое не помещаются на экране дисплея.
HEX - выводит на экран монитора цифры после бинаризации в шестнадцатеричном виде для использования их в качестве эталонна;
м3 на монитор - выводит результаты сохраненных данных в кольцевом буфере на монитор.
Также выводится информация о текущих распознанных цифрах, расстояние Хемминга и число совпадений распознанной цифры за 10 циклов в правой нижней части экрана. Если значения расстояние Хемминга или числа совпадений находится вне пределов, то цифра соответствующая этим значениям будут высвечиваться как символ знака вопроса, красного цвета.
В верхней части показаны текущие значения шкалы счетчика.
Слева зеленым мы видим распознанные показания. Именно эти показания и сохраняются в кольцевом буфере и по ним строится график на другой странице приложения Virtuino работающего на смартфоне.
Спасибо всем, кто помогал мне в реализации данного проекта
Разработка устройства еще в стадии изготовления и доработки, не оптимальная и содержит много неточностей.
Код программы представлен ниже
/*********
Rui Santos
Complete project details at https://RandomNerdTutorials.com/esp32-cam-video-streaming-web-server-camera-home-assistant/
*********/
#include "esp_camera.h"
#include <WiFi.h>
#include "esp_timer.h"
#include "img_converters.h"
#include "Arduino.h"
#include "fb_gfx.h"
#include "soc/soc.h" //disable brownout problems
#include "soc/rtc_cntl_reg.h" //disable brownout problems
#include "esp_http_server.h"
#include "TFT_22_ILI9225.h"
#include <SPI.h>
// Include font definition files
// NOTE: These files may not have all characters defined! Check the GFXfont def
// params 3 + 4, e.g. 0x20 = 32 = space to 0x7E = 126 = ~
#include "fonts/FreeSansBold24pt7b.h"
#define TFT_CS 15
#define TFT_RST -1 //
#define TFT_RS 2 //RS
#define TFT_SDI 12 // Data out SDA MOSI SDI
#define TFT_CLK 14 // Clock out CLK
TFT_22_ILI9225 tft = TFT_22_ILI9225(TFT_RST, TFT_RS, TFT_CS, TFT_SDI, TFT_CLK, 0, 200);
#include "virtuino_pins.h"
#include "time.h"
const char* ntpServer = "pool.ntp.org";
const long gmtOffset_sec = 7200;
const int daylightOffset_sec = 3600; //летнее время 3600;
struct tm timeinfo; //структура времени записи кольцевого буфера
#define info_first 2 ////строка вывода результатов ширины и высоты цифр
#define info_result 58 //строка вывода результатов распознавания
#define info_gistorgamm 105 //строка вывода гистограммы
#define info_Hemming 130 //строка вывода информационнии о расстоянии Хемминга
#define info_frequency 145 //строка вывода информационнии о частоте совпадения
#define info_britnes 115 //строка вывода информационнии об уровнях яркости
#define info_time 160 //строка вывода на дисплей м3/мин сек и времени
//++++++++++++++++++++++++++++++++++++++++++ снизить до 50
#define Hemming_level 80 //Значенее расстояния Хемминга которое считается максимально допустимым при распознавании 50
#define width_letter 26 //ширина цифр в пикселях
#define number_letter 8 //число цифр в шкале
#define height_letter 26 //высота по y - совпадает с высотой эталона
#define average_count 10 //количество усреднений результатов распознавания
#define average_count_level average_count-3 //число усреднений, которое принимается за положительное при распознавании
#define F_HEIGHT 176 //высота обработки изображения совпадает с высотой дисплея 2.0
#define F_WIDTH 320 //ширина обработки изображения совпадает с шириной изображения камеры
#include "sample.h" //образцы эталонов
uint16_t Y_first, Y_last; //положение окна расположения цифр в буфере камеры
uint16_t pixel_level = 0; //пороговый уровень пикселей на изображении определеный методом Отцу
uint16_t max_letter_x[number_letter]; //массив середины цифры по оси Х
uint32_t l_32[number_letter][F_HEIGHT]; //массив после перевода распознаваемых цифр в 32 битное число. Запас по высоте равен высоте экрана
uint8_t result[average_count][number_letter]; //накопление результатов распознавания цифр со шкалы
uint16_t *frame_buf; //указатель на буфер для накопления кадров камеры
#define max_shift 9*3 //число вариантов сдвига перемещения эталона
int shift_XY[max_shift][2] = { //содержит сдвиг по оси X Y
{0, 0},
{0, 1}, //up
{0, 2}, //up
{0, 3}, //up
{0, 4}, //up
{0, -1}, //down
{0, -2}, //down
{0, -3}, //down
{0, -4}, //down
{1, 0}, //right
{1, 1}, //right up
{1, 2}, //right up
{1, 3}, //right up
{1, 4}, //right up
{1, -1}, //right down
{1, -2}, //right down
{1, -3}, //right down
{1, -4}, //right down
{ -1, 0}, //left
{ -1, 1}, //left up
{ -1, 2}, //left up
{ -1, 3}, //left up
{ -1, 4}, //left up
{ -1, -1},//left down
{ -1, -2},//left down
{ -1, -3},//left down
{ -1, -4},//left down
};
struct Hemming_struct { //структура расстояний Хемминга для всех цифр шкалы
uint8_t result; // опознанная цифра
uint16_t min_Hemming; // расстояния Хемминга для опознанной цифры
uint8_t etalon_number; // номер эталона в массиве эталонов
uint8_t frequency; //число совпадений при опознании
uint8_t next_result; // значенее следующей цифры
uint16_t next_min_Hemming; // расстояния Хемминга для следующей цифры
uint8_t dig_defined; //набор символов, который определяется автоматически после распознавания
uint8_t britnes_digital; //яркость для каждой буквы
uint16_t x_width; //определенная ширина цифры
} Hemming[number_letter];
uint8_t frequency[number_of_samples][number_letter]; //подсчет максимального числа совпадений результатов распознавания
camera_fb_t * fb; //для работы камеры указатель на структуру буфер
sensor_t * s; //для работы камеры указаитель на структуру сенсора
// Replace with your network credentials
const char* ssid = "********";
const char* password = "*********";
#define PART_BOUNDARY "123456789000000000000987654321"
// This project was tested with the AI Thinker Model, M5STACK PSRAM Model and M5STACK WITHOUT PSRAM
#define CAMERA_MODEL_AI_THINKER
#define PWDN_GPIO_NUM 32
#define RESET_GPIO_NUM -1
#define XCLK_GPIO_NUM 0
#define SIOD_GPIO_NUM 26
#define SIOC_GPIO_NUM 27
#define Y9_GPIO_NUM 35
#define Y8_GPIO_NUM 34
#define Y7_GPIO_NUM 39
#define Y6_GPIO_NUM 36
#define Y5_GPIO_NUM 21
#define Y4_GPIO_NUM 19
#define Y3_GPIO_NUM 18
#define Y2_GPIO_NUM 5
#define VSYNC_GPIO_NUM 25
#define HREF_GPIO_NUM 23
#define PCLK_GPIO_NUM 22
static const char* _STREAM_CONTENT_TYPE = "multipart/x-mixed-replace;boundary=" PART_BOUNDARY;
static const char* _STREAM_BOUNDARY = "\n--" PART_BOUNDARY "\n";
static const char* _STREAM_PART = "Content-Type: image/jpeg\nContent-Length: %u\n\n";
httpd_handle_t stream_httpd = NULL;
#include <Ticker.h> //esp32 library that calls functions periodically
Ticker Gas_minute_Ticker; //используется для расчета объма газа каждую минуту
#define size_m3 2048 //размер кольцевого буфера для хранения данных каждую минуту должен быть 256, 512, 1024 ...
//структура для сохранения информации о расчете объема газа
struct Gas_struct {
uint32_t m3; //значенее объма газа умноженное на 100
uint32_t minutes; //разница в минутах между предыдущим и текущим измерением
} Gas[size_m3];
uint16_t position_m3 = 0; //позиция сохранения данных
//---------------------------------------------------- m3_calculate
void m3_calculate() {
uint32_t k = 1000000; //коэффициент перевода цифр шкалы в число
uint32_t current_m3 = 0; //текущее значенее
bool flag = true; //флаг рапознаввания
uint16_t pos_1 = (position_m3 - 1) & (size_m3 - 1); //позиция в буфере места записи минус 1 - педыдущее значение
for (uint8_t dig = 0; dig < number_letter - 1; dig++) { //проверка на все кроме последней цифры -1
if ((Hemming[dig].frequency < average_count_level) || (Hemming[dig].min_Hemming > Hemming_level) || Hemming[dig].dig_defined == 10) {
//нет правильного результата увеличим пропущенное время если это не начало - предыдущее значение минут равно 0
if (Gas[pos_1].m3 == 0) Gas[position_m3].minutes++;
current_m3 = 0; //обнуляем, т.к. нет правильного результата
flag = false; //сбросим флаг - не распознали
break; //выйти из цикла
}
current_m3 += Hemming[dig].dig_defined * k; //берем опознанное значенее и переводим в число
k = k / 10; //уменьшаем коэффициент для следующего числа
}
Serial.printf("flag=%d current_m3= %d minutes=%d position_m3=%d pos_1=%d\n", flag, current_m3, Gas[position_m3].minutes, position_m3, pos_1);
if (flag) { //распознали сохраняем
if (((current_m3 - Gas[pos_1].m3 < 0) || (current_m3 - Gas[pos_1].m3 > 6)) && (Gas[pos_1].m3 != 0)) { //ошибка распознавания вне пределов
//если разница между предыдущем и текущим значением меньше нуля - текущее определили не верно
//если разница больше 0,06 - ошибка определения - текущее значение определили не верно - за 1 минуту не может быть больше 0,05
//при начальном заполнении буфера первый раз будет давать ошибку
V[V_26_error_recognition]++; //увеличим счетчик ошибок неправльно распознанных
Serial.printf("Значение %d м3 вне пределов на минуте %d\n", current_m3, Gas[pos_1].minutes);
}
else { //значения совпадают или нормально измененные
V[V_26_error_recognition] = 0.0; //сбросим счетчик ошибок неверно распознанных
}
Serial.printf("Предыдущее %d текущее %d м3 позиция %d минут %d\tразница=%d\n", Gas[pos_1].m3, current_m3, position_m3, Gas[pos_1].minutes, current_m3 - Gas[pos_1].m3);
if (Gas[pos_1].m3 == current_m3) {
//если совпало с предыдущим просто увеличим время простоя на 1 минуту или на время пропущенных минут
//если ранее были нераспознанные минуты, то прибавим к пропущенному времени
// Serial.printf("Совпадение значений current_m3 %.2f м3 увеличим предыдущее время Gas[pos_1].minutes=%d разница %f\n",current_m3, Gas[pos_1].minutes,current_m3 - Gas[pos_1].m3);
Gas[pos_1].minutes += Gas[position_m3].minutes;
Gas[position_m3].minutes = 1; //возобновим подсчет времени сначала для следующего элемента
V[V_m3] = current_m3;
V[V_m3_minutes] = Gas[pos_1].minutes;
}
else { //не совпали значения - есть изменения сохраним не обращая внимание на пределы и переход к следующему элементу
Gas[position_m3].m3 = current_m3;
V[V_m3] = current_m3;
V[V_m3_minutes] = Gas[position_m3].minutes;
position_m3 = (position_m3 + 1) & (size_m3 - 1); //переход к следующему элементу с учетом конца буффера;
}
Gas[position_m3].minutes = 1; //подсчет времени сначала для текущего элемента
Gas[position_m3].m3 = 0;
} //распознали сохраняем
pos_1 = (position_m3 - 1) & (size_m3 - 1); //позиция в буфере места записи минус 1 - педыдущее значенее
uint16_t pos_2 = (position_m3 - 2) & (size_m3 - 1); //позиция в буфере места записи минус 2
if (Gas[pos_2].minutes != 0) { //если уже сохранено не менее 2-х элементов
Serial.printf("Gas[pos_2].m3= %d Gas[pos_1].m3= %d minutes=%d difference_m3=%d position-1=%d position-2=%d\n",
Gas[pos_2].m3, Gas[pos_1].m3, Gas[pos_1].minutes, (Gas[pos_1].m3 - Gas[pos_2].m3) / Gas[pos_1].minutes, pos_1, pos_2);
}
else
Serial.printf("current_m3= %d minutes=%d\n", Gas[pos_1].m3, Gas[pos_1].minutes);
}
//---------------------------------------------------- m3_calculate
//---------------------------------------------------- print_m3
void print_m3() {
//вывести на монитор накопленные результаты m3
time_t now;
uint32_t all_minutes = 0; //для подсчета времени с начала отсчета
uint16_t pos_1 = (position_m3 - 1) & (size_m3 - 1); //позиция в буфере места записи минус 1 - педыдущее значенее
if (Gas[pos_1].minutes == 0) return; //не выводить если начало - нет предыдущего значения
Serial.printf("i\tm3*100\tminutes\t\tmax number=%d\n", position_m3);
for (uint32_t i = 0; i < size_m3; i++) {
uint16_t pos = (position_m3 + i) & (size_m3 - 1); //позиция в буфере после места записи
if (Gas[pos].m3 == 0) continue; //если обнаружили конец буфера продолжим
// Serial.printf("i=%d pos=%d position_m3+i=%d Gas[pos].minutes=%d Gas[pos].m3=%.2f\n",i, pos , position_m3+i,Gas[pos].minutes,Gas[pos].m3);
all_minutes += Gas[i].minutes; //подсчет общего времени суммируем все
Serial.printf("%d\t%d\t%d\n", pos, Gas[pos].m3, Gas[pos].minutes);
}
V[V_SH_M3] = 0; //выведем один раз
if (!getLocalTime(&timeinfo)) { //получим время записи сохранения м3
Serial.printf("Failed to obtain time\n");
}
time(&now);
now -= all_minutes * 60; //отнимим прошедшие минуты о получим начало отсчета
localtime_r(&now, &timeinfo);
Serial.printf("\nНачало записи %02d.%02d.%4d %02d:%02d:%02d\n", timeinfo.tm_mday, timeinfo.tm_mon + 1, timeinfo.tm_year + 1900,
timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec);
}
//---------------------------------------------------- print_m3
//---------------------------------------------------- printBinary
#include <limits.h>
template<typename T>
void printBinary(T value, String s) {
for ( size_t mask = 1 << ((sizeof(value) * CHAR_BIT) - 1); mask; mask >>= 1 ) {
Serial.printf("%c", value & mask ? '1' : ' ');
}
Serial.printf("%s", s);
}
//---------------------------------------------------- printBinary
//---------------------------------------------------- britnes_digital
uint16_t find_middle_britnes_digital(uint16_t *fr_buf, bool show) {
//расчет средней яркости пикселей для каждой цифры после того как определили их место
for (uint8_t dig = 0; dig < number_letter; dig++) { //поочередно обрабатываем каждое знакоместо отельно
float britnes = 0;
uint16_t w_l = width_letter;
int x1 = max_letter_x[dig] - width_letter / 2; //для первой цифры размер может быть меньше установленной ширины
if (x1 < 0) {
w_l += x1; //x1 имеет отрицательное значенее поэтому суммируем
if (show) Serial.printf("x1=%d max_letter_x[dig]=%d w_l=%d\n", x1, max_letter_x[dig], w_l);
x1 = 0;
}
for (uint16_t y = Y_first; y < Y_last; y++) { //перебор по столбцу в пределах высоты буквы
for (uint16_t x = x1; x < max_letter_x[dig] + width_letter / 2; x++) { //перебор по строке в пределах ширины одной цифры
uint32_t i = (y * F_WIDTH + x);
britnes += fr_buf[i]; //суммируем все значения
}
}
Hemming[dig].britnes_digital = (int)(britnes / (w_l * (Y_last - Y_first))); //для первой цифры ширина может быть меньше
if (show)
Serial.printf("dig=%d britnes=%d pixel_level=%d\n", dig, (int)(britnes / (width_letter * (Y_last - Y_first))), pixel_level);
}
}
//---------------------------------------------------- britnes_digital
//---------------------------------------------------- find_middle_level_image
uint16_t find_middle_level_image(uint16_t *fr_buf, bool show) {
//найти уровень яркости изображения по Отцу
float av = 0;
uint16_t min1 = fr_buf[0];
uint16_t max1 = fr_buf[0];
uint32_t f_size = F_WIDTH * F_HEIGHT;
//найти средний уровень пикселей окно табло - засвечено
// Посчитаем минимальную и максимальную яркость всех пикселей
for (uint32_t i = 0; i < f_size; i++) {
av += fr_buf[i];
if (fr_buf[i] < min1) min1 = fr_buf[i];
if (fr_buf[i] > max1) max1 = fr_buf[i];
}
av = av / f_size;
// Гистограмма будет ограничена снизу и сверху значениями min и max,
// поэтому нет смысла создавать гистограмму размером 256 бинов
int histSize = max1 - min1 + 1;
int *hist = (int *) heap_caps_calloc(histSize * sizeof(int), 1, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
if (hist == NULL) {
Serial.printf("Problem with heap_caps_malloc find_middle_level_y\n");
return 0;
}
// Заполним гистограмму нулями
for (int t = 0; t < histSize; t++)
hist[t] = 0;
// И вычислим высоту бинов
for (int i = 0; i < f_size; i++)
hist[fr_buf[i] - min1]++;
// Введем два вспомогательных числа:
int m = 0; // m - сумма высот всех бинов, домноженных на положение их середины
int n = 0; // n - сумма высот всех бинов
for (int t = 0; t <= max1 - min1; t++)
{
m += t * hist[t];
n += hist[t];
}
float maxSigma = -1; // Максимальное значение межклассовой дисперсии
int threshold = 0; // Порог, соответствующий maxSigma
int alpha1 = 0; // Сумма высот всех бинов для класса 1
int beta1 = 0; // Сумма высот всех бинов для класса 1, домноженных на положение их середины
// Переменная alpha2 не нужна, т.к. она равна m - alpha1
// Переменная beta2 не нужна, т.к. она равна n - alpha1
// t пробегается по всем возможным значениям порога
for (int t = 0; t < max1 - min1; t++)
{
alpha1 += t * hist[t];
beta1 += hist[t];
// Считаем вероятность класса 1.
float w1 = (float)beta1 / n;
// Нетрудно догадаться, что w2 тоже не нужна, т.к. она равна 1 - w1
// a = a1 - a2, где a1, a2 - средние арифметические для классов 1 и 2
float a = (float)alpha1 / beta1 - (float)(m - alpha1) / (n - beta1);
// Наконец, считаем sigma
float sigma = w1 * (1 - w1) * a * a;
// Если sigma больше текущей максимальной, то обновляем maxSigma и порог
if (sigma > maxSigma)
{
maxSigma = sigma;
threshold = t;
}
}
// Не забудем, что порог отсчитывался от min, а не от нуля
threshold += min1;
heap_caps_free(hist); //освободить буфер
if (show)
Serial.printf("min =%d max=%d cреднее по уровню=%.0f threshold= %d\n", min1, max1, av, threshold);
// Все, порог посчитан, возвращаем его наверх :)
return (uint16_t)(threshold);
}
//---------------------------------------------------- find_middle_level_image
//---------------------------------------------------- find_digits_y
void find_digits_y (uint16_t *fr_buf, uint16_t mid_level, uint8_t add_mid_level, bool show) {
//fr_buf буфер с изображением формата uint16_t
//mid_level средний уровень яркости изображения
//add_mid_level повышение уровня для устранения засветки при анализе
//show вывести информацию на экран
//поиск положения цифр по высоте Y
#define Y_FIRST_LOOK 10 //ограничим начало поиска 10 пикселями в строке
#define Y_LAST_LOOK 100 //ограничим конец поиска 100 пикселями в строке
Y_first = 0;
Y_last = 0;
float av = 0;
char buf[50]; //буфер для перевода значений строку и печати на дисплеи
/*
//проверка на максимум уровня не должен быть больше 255
if (mid_level + add_mid_level > 255)
add_mid_level = (255 - mid_level);
*/
//поиск среднего уровня
for (uint8_t y = Y_FIRST_LOOK; y < Y_LAST_LOOK; y++) { //только в пределах экрана по высоте 10-100 строки
for (uint16_t x = 0; x < tft.maxX(); x++) { //ограничим шириной экрана, а не всем изображением F_WIDTH
uint32_t i = (y * F_WIDTH + x);
if (fr_buf[i] > mid_level + add_mid_level) av++;
}
}
av = (uint16_t) (av / (Y_LAST_LOOK - Y_FIRST_LOOK));
for (uint8_t y = Y_FIRST_LOOK; y < Y_LAST_LOOK; y++) { //только в пределах экрана по высоте 10-100 строки
float av1 = 0;
for (uint16_t x = 0; x < tft.maxX(); x++) { //ограничим шириной экрана, а не всем изображением F_WIDTH
uint32_t i = (y * F_WIDTH + x);
if (fr_buf[i] > mid_level + add_mid_level) av1++;
}
if (av < av1) {
if (show) {
Serial.printf("av=%.0f av1=%.0f Y = %d\n", av, av1, y);
}
if (Y_first == 0) Y_first = y;
Y_last = y;
}
}
uint8_t Y_mid = Y_first + ((Y_last - Y_first) >> 1);
if(Y_last - Y_first != height_letter) {
Y_last = Y_mid + (height_letter >> 1);
Y_first = Y_mid - (height_letter >> 1);
}
tft.drawLine(0, Y_first, tft.maxX() - 1, Y_first, COLOR_YELLOW);
tft.drawLine(0, Y_last, tft.maxX() - 1, Y_last, COLOR_YELLOW);
tft.drawLine(0, Y_mid, tft.maxX() - 1, Y_mid, COLOR_CYAN);
/*
//вывод на дисплей индивидуальных значений яркости
tft.setFont(Terminal6x8 ); //10 pixel
tft.fillRectangle (0, info_britnes, tft.maxX(), 20, COLOR_BLACK); //очистить часть экрана
tft.setColor(COLOR_WHITE);
tft.setCursor(0, info_britnes);
for (uint8_t dig = 0; dig < number_letter; dig++) {//вывести значения яркости
tft.drawTextf("%d ", Hemming[dig].britnes_digital);
}
*/
//вывод на дисплей результатов сохраненных значений
tft.setFont(Terminal6x8); //10 pixel
tft.fillRectangle (0, info_time - 2, tft.maxX(), info_time + 10, COLOR_BLACK); //очистить часть экрана
uint8_t pos_1 = (position_m3 - 1) & (size_m3 - 1); //позиция в буфере после места записи -1
uint8_t pos_2 = (position_m3 - 2) & (size_m3 - 1); //позиция в буфере после места записи -2
if (Gas[pos_2].minutes != 0) { //если уже сохранено не менее 2-х элементов
sprintf(buf, "%4d mins %4.2f m3/h\0", Gas[pos_1].minutes, (Gas[pos_1].m3 - Gas[pos_2].m3) / (Gas[pos_1].minutes * 100.0));
tft.drawText(0, info_time, buf, COLOR_WHITE);
}
else {
sprintf(buf, "%4d mins %4.2f m3\0", Gas[pos_1].minutes, Gas[pos_1].m3 / 100.0);
tft.drawText(0, info_time, buf, COLOR_WHITE);
}
sprintf(buf, "%02d:%02d:%02d\0", timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec);
if (!getLocalTime(&timeinfo)) {
Serial.printf("Failed to obtain time\n");
tft.drawText(tft.maxX() - tft.getTextWidth(buf), info_time, buf, COLOR_RED); //9 * tft.getCharWidth(48)
}
else {
sprintf(buf, "%02d:%02d:%02d\0", timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec);
tft.drawText(tft.maxX() - tft.getTextWidth(buf), info_time, buf, COLOR_YELLOW);
}
uint16_t x_width_min = F_WIDTH; //ширина экрана
uint16_t x_width_max = 0; //минимальное значенее
for (uint8_t dig = 0; dig < number_letter; dig++) {
//поиск максимальной и минимальной ширины определнной цифры
if (x_width_min > Hemming[dig].x_width) x_width_min = Hemming[dig].x_width;
if (x_width_max < Hemming[dig].x_width) x_width_max = Hemming[dig].x_width;
}
uint16_t next_x = 0;
sprintf(buf, "Y_m=%2d", Y_mid);
if ((Y_last - Y_first) != sample_height)
tft.drawText(next_x, info_first, buf, COLOR_YELLOW);
else
tft.drawText(next_x, info_first, buf, COLOR_GREEN);
next_x += tft.getTextWidth(buf);
sprintf(buf, " X_dmax=%d", x_width_max);
if ((x_width_max > width_letter) || (x_width_max <= x_width_min)) //ширина не должна быть больше ширины буквы
tft.drawText(next_x, info_first, buf, COLOR_YELLOW);
else
tft.drawText(next_x, info_first, buf, COLOR_GREEN);
next_x += tft.getTextWidth(buf);
sprintf(buf, " X_dmin=%d", x_width_min);
if ((x_width_min < 3) || (x_width_min > width_letter)) //ширина не должна быть меньше 3 пикселей
tft.drawText(next_x, info_first, buf, COLOR_YELLOW);
else
tft.drawText(next_x, info_first, buf, COLOR_GREEN);
if (show) {
Serial.printf(" Y_first = %d Y_last = %d\n", Y_first, Y_last);
}
}
//---------------------------------------------------- find_digits_y
//---------------------------------------------------- find_max_digital_X
void find_max_digital_X(uint16_t *fr_buf, uint16_t mid_level, uint8_t add_mid_level, bool show) {
//fr_buf буфер с изображением формата uint16_t
//mid_level средний уровень изображения
//add_mid_level повышение следующего уровня для устранения засветки при отображении
//show вывести информацию на экран
//поиск границ цифр в найденной строке по X
uint8_t letter[F_WIDTH]; //массив для поиска максимума по оси Х
/*
//проверка на максимум уровня
if (mid_level + add_mid_level > 255)
add_mid_level = (255 - mid_level);
*/
//строим гистограмму подсчет количества единиц по столбцу
for (uint16_t x = 0; x < F_WIDTH; x++) { //перебор по строке
letter[x] = 0; //обнуляем начальное значение
for (uint16_t y = Y_first; y < Y_last + 1; y++) { //ищем только в пределах обнаруженных цифр
uint16_t i = (y * F_WIDTH + x);
if (fr_buf[i] > mid_level + add_mid_level) {
letter[x]++;
}
}
// if(show) Serial.printf("x=%3d letter=%d\n",x,letter[x]);
}
if (show) {
//вывод гистограммы на дисплей с учетом смещения по оси Х
tft.fillRectangle (0, info_Hemming - 30, tft.maxX(), info_Hemming - 2, COLOR_BLACK); //очистить часть экрана
for (uint16_t x = V[V_offset_x_digital]; x < F_WIDTH; x++) { //перебор по строке V[V_offset_x]
if (letter[x] != 0)
tft.drawLine(x - V[V_offset_x_digital], info_Hemming - 30, x - V[V_offset_x_digital], 100 + letter[x], COLOR_CYAN); //Y_last + 10 V[V_offset_x]
}
}
//уточним центры цифр по гистограмме
uint16_t x1 = 0, x2 = 0; //начало и конец цифры
uint16_t dig = 0; //номер найденной цифры от 0 до number_letter-1
for (uint16_t x = 0; x < F_WIDTH; x++) { //перебор по строке
if (letter[x] > 2) { //если есть пиксели найти начало больше 2 пикселей
if (x1 != 0) x2 = x; //если было найдено начало установить конец
else x1 = x; //установить начало
}
else { //нет пикселей
if (x1 != 0 && x2 != 0) { //если были ранее определены пределы показать границы
if (show) Serial.printf("x1=%4d x2=%4d x_mid=%4d d_x=%d dig=%d\n", x1, x2, ((x2 - x1) >> 1) + x1, (x2 - x1), dig);
if (dig > number_letter - 1) { //усли найдено больше чем цифр в шкале 8 number_letter - 1
if (show) Serial.printf("Найдено больще цифр чем в шкале по оси Х!!! %d\n", dig);
return;
}
max_letter_x[dig] = ((x2 - x1) >> 1) + x1;
Hemming[dig].x_width = (x2 - x1); //сохраним значенее ширины буквы
dig++;
//линия на +/- 5 пикселей от Y_last и Y_first
tft.drawLine(((x2 - x1) >> 1) + x1 - V[V_offset_x_digital], Y_first - 5, ((x2 - x1) >> 1) + x1 - V[V_offset_x_digital], Y_last + 5, COLOR_BLUE);
tft.drawLine(x1 - V[V_offset_x_digital], Y_first - 5, x1 - V[V_offset_x_digital], Y_last + 5, COLOR_OLIVE);
tft.drawLine(x2 - V[V_offset_x_digital], Y_first - 5, x2 - V[V_offset_x_digital], Y_last + 5, COLOR_OLIVE);
/*
//построение диаграммы по оси Y между x1 и x2
for (uint16_t y = Y_first; y < Y_last; y++) {
uint8_t y_l = 0;
for (uint16_t x = x1; x < x2; x++) {
uint16_t i = (y * F_WIDTH + x);
if (fr_buf[i] > mid_level + add_mid_level) {
y_l++;
}
}
//вывести гистограмму по высоте на экран
// if (SH_0_1 == 1) {
// for (uint8_t i = 0; i < y_l; i++)
// Serial.printf("1");
// }
//// tft.drawFastHLine(x1 - V[V_offset_x_digital], Y_first - 65 + y, y_l, COLOR_YELLOW);
// if (SH_0_1 == 1) Serial.printf("\n");
}
// if (SH_0_1 == 1) Serial.printf("\n");
//построение диаграммы по оси Y между x1 и x2
*/
}
//обнулим значение для следующей цифры
x1 = 0;
x2 = 0;
}
}
if (dig != number_letter)
if (show) Serial.printf("Не все цифры шкалы найдены по оси Х!!! %d\n", dig);
}
//---------------------------------------------------- find_max_digital_X
//---------------------------------------------------- sum_one
uint8_t sum_one(uint32_t d) { //суммирование всех 1 в 32 битном числе
uint8_t r = 0;
for (uint8_t i = 0; i < 32; i++) {
r += d & 0x1;
d = d >> 1;
}
return r;
}
//---------------------------------------------------- sum_one
//---------------------------------------------------- compare
uint8_t compare(uint8_t y, uint8_t samp_dig, uint8_t dig, int X_shift, int Y_shift, bool show) {
//y положенее по оси Y
//samp_dig номер эталона
//dig - какую цифру обрабатываем
//X_shift сдвиг по оси +/- Х
//Y_shift сдвиг по оси +/- Y
//show выводить на экран
uint32_t samp = sample[samp_dig][y];//центры эталона и сравниваемая цифра совпадают
//<< 5; нужно подготовить центры середина эталонна полученная из знакогенератора 8, середина цифры от камеры 13
uint32_t samp1;
if ((y + Y_shift < F_HEIGHT) || (y + Y_shift > 0)) {
if (X_shift < 0)
samp1 = l_32[dig][y + Y_shift] >> abs(X_shift); //образец который сравниваем может быть отрицательное значенее
else
samp1 = l_32[dig][y + Y_shift] << X_shift; //образец который сравниваем
}
else samp1 = 0;
if (show)
printBinary(samp1 ^ samp, "\t");
return sum_one(samp1 ^ samp);
}
//---------------------------------------------------- compare
//---------------------------------------------------- image_recognition
uint8_t image_recognition(uint8_t dig, uint8_t dig_show) {
//dig - какую цифру обрабатываем
//dig_show какую цифру отображаем если > 8 = не выводим
//распознавание цифр
//сравнить с эталоном - расчет расстояния Хемминга
uint8_t min_dig_number = number_of_samples; //присвоим максимальное значенее
//вывод цифры со шкалы в двоичном виде
if (dig == dig_show) {
Serial.printf("------------------------------\n");
for (uint8_t y = 0; y < Y_last - Y_first; y++) { //перебор по Y
printBinary(l_32[dig][y], "\n");
}
Serial.printf("------------------------------\n");
}
//вывод цифры со шкалы в HEX формате
if (V[V_SH_HEX] == 1) {
Serial.printf("{//%d\n", dig);
for (uint8_t y = 0; y < Y_last - Y_first; y++) { //перебор по Y
Serial.printf("0x%010lx,\t//", l_32[dig][y]);
printBinary(l_32[dig][y], "\n");
}
Serial.printf("},\n");
}
uint32_t min_Hemming[number_of_samples]; //массив минимальных расстояний Хемминга для всех эталонов
for (uint8_t samp_dig = 0; samp_dig < number_of_samples; samp_dig++) { //перебор по все эталлонам
uint16_t shift[max_shift]; //хранение результатов расчетов расстояния Хеминга после всех вариантов сдвигов
for (uint8_t i = 0; i < max_shift; i++)
shift[i] = 0; //обнулим для накопления результатов расчета расстояния Хеминга
for (uint8_t y = 0; y < sample_height; y++) { //перебор по Y - высоте эталона и образца
for (uint8_t i = 0; i < max_shift; i++) //перебор по всем сдвигам
shift[i] += compare(y, samp_dig, dig, shift_XY[i][0], shift_XY[i][1], dig == dig_show);
if (dig == dig_show) Serial.printf("\n");
}
if (dig == dig_show) {
Serial.printf("Etalon Digit=%d", samp_dig);
for (uint8_t i = 0; i < max_shift; i++)
Serial.printf("\tshift=%3d %d %d\t\t\t", shift[i], shift_XY[i][0], shift_XY[i][1]);
Serial.printf("\n");
}
//поиск минималного значения после сдвигов
min_Hemming[samp_dig] = shift[0];
for (uint8_t i = 0; i < max_shift; i++) {
if (min_Hemming[samp_dig] > shift[i])
min_Hemming[samp_dig] = shift[i];
}
}
uint32_t min_dig = 1024; //будет содержать минимальное значение расстояния Хемминга
for (uint8_t samp_dig = 0; samp_dig < number_of_samples; samp_dig++) { //перебор по все эталлонам
if (min_dig >= min_Hemming[samp_dig]) {
min_dig = min_Hemming[samp_dig];
min_dig_number = sample_equation[samp_dig]; //получить цифру соответсвия
Hemming[dig].etalon_number = samp_dig; //номер эталона в массиве
}
if (dig == dig_show)
Serial.printf("Etalon=%d\tmin_Hemming=%d\n", sample_equation[samp_dig], min_Hemming[samp_dig]);
}
if (dig == dig_show)
Serial.printf("\n***** Found Digit=%d\tmin_Hemming= %d *****\n", min_dig_number, min_dig);
Hemming[dig].min_Hemming = min_dig; //сохранить значениее расстояния Хемминга
//поиск следующего минимума
min_dig = 1024; //будет содержать минимальное значение расстояния Хемминга
Hemming[dig].next_result = 10; //всего цифры от 0 до 9
for (uint8_t samp_dig = 0; samp_dig < number_of_samples; samp_dig++) { //перебор по все эталлонам
// Serial.printf("samp_dig=%d min_dig_number=%d min_Hemming=%d min_dig=%d\n",samp_dig,min_dig_number,min_Hemming[samp_dig], min_dig);
if (sample_equation[samp_dig] == min_dig_number) continue; //если уже найденный минимум пропустить
if (min_dig >= min_Hemming[samp_dig]) {
min_dig = min_Hemming[samp_dig];
Hemming[dig].next_result = sample_equation[samp_dig]; //значенее опознанной цифры
}
}
Hemming[dig].next_min_Hemming = min_dig; //сохранить значение расстояния Хемминга
return min_dig_number;
}
//---------------------------------------------------- image_recognition
//---------------------------------------------------- convert_to_32
void convert_to_32(uint16_t *fr_buf, uint16_t mid_level, uint8_t add_mid_level, bool show) {
/*
//проверка на максимум уровня
if (mid_level + add_mid_level > 255)
add_mid_level = (255 - mid_level);
*/
for (uint8_t dig = 0; dig < number_letter; dig++) { //всего 8 цифр в шкале последовательно обрабатываем каждую
for (uint16_t y = Y_first; y < Y_last; y++) { //перебор по столбцу
l_32[dig][y - Y_first] = 0;
int x1 = max_letter_x[dig] - width_letter / 2; //для первой цифры размер может быть меньше установленной ширины
if (x1 < 0) x1 = 0;
for (uint16_t x = x1; x < max_letter_x[dig] + width_letter / 2; x++) { //перебор по строке в пределах одной цифры
l_32[dig][y - Y_first] = l_32[dig][y - Y_first] << 1; //сдвиг на 1 позицию
uint32_t i = (y * F_WIDTH + x);
if (fr_buf[i] > Hemming[dig].britnes_digital + add_mid_level) { //индивидуальный уровень яркости для каждой цифры
// if (fr_buf[i] > mid_level + add_mid_level) { //средний уровень
if (show || V[V_SH_0_1] != 0) Serial.printf("1");
l_32[dig][y - Y_first]++;
}
else {
if (show) Serial.printf(" ");
if (V[V_SH_0_1] == 1) Serial.printf(" ");
if (V[V_SH_0_1] == 2) Serial.printf("0");
}
}
if (show) Serial.printf("|0x%010lx\n", l_32[dig][y - Y_first]);
if (V[V_SH_0_1] != 0) Serial.printf("\n");
}
if (show) Serial.printf("Letter box middel = %d d_x = %d d_y =%d mid_line_y=%d\n", max_letter_x[dig], width_letter, Y_last - Y_first, Y_first + (Y_last - Y_first) / 2);
if (V[V_SH_0_1] != 0) Serial.printf("\n");
}
}
//---------------------------------------------------- convert_to_32
//---------------------------------------------------- dispalay_ttf_B_W
esp_err_t dispalay_ttf_B_W(uint16_t *fr_buf, uint16_t mid_level, uint8_t add_mid_level) {
//fr_buf буфер с изображением формата uint16_t
//X0 начальная кордината вывода по оси Х
//Y0 начальная кордината вывода по оси Y
//mid_level средний уровень изображения применятся индивидуальный для каждой цифры
//add_mid_level повышение следующего уровня для устранения засветки при отображении
//зарезервировтать паять для буфера дисплея
uint16_t W = tft.maxX();
uint16_t H = tft.maxY();
uint16_t *disp_buf = (uint16_t *)heap_caps_calloc(W * H * 2, 1, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
if (disp_buf == NULL) {
Serial.printf("malloc failed f_b\n");
return ESP_FAIL;
}
for (int y = 0; y < H; y++) { //выводим только по высоте экрана //tft.maxX() tft.maxY()
for (int x = 0; x < W; x++) {
uint8_t dig = 0; //номер цифры табло
uint32_t i = (y * F_WIDTH + x + V[V_offset_x_digital]);
uint32_t j = (y * W + x);
uint16_t color;
if (s->pixformat == PIXFORMAT_GRAYSCALE) { //если GRAYSCALE преобразовать в RGB565 каждый байт буфера
color = (((uint16_t)(fr_buf[i]) & 0xF8) << 8) | (((uint16_t)(fr_buf[i]) & 0xFC) << 3) | ((uint16_t)(fr_buf[i]) >> 3);
}
else
color = fr_buf[i];
if (V[V_GBW] == 0) {
if (x > max_letter_x[dig] + width_letter / 2) { //если текущее значенее в пределах цифры, то использовать соответствующее значенее яркости
dig++; //перейти к следующей цифре
if (dig > number_letter) dig = number_letter - 1; //если больше цифр то принимать яркость последней
}
if (fr_buf[i] < Hemming[dig].britnes_digital + add_mid_level) //индивидуальный уровень яркости для каждой цифры
*(disp_buf + j) = COLOR_BLACK;
else
*(disp_buf + j) = COLOR_WHITE;
}
else *(disp_buf + j) = color;
}
}
if (V[V_GBW] == 2) //если выводить полный экран
tft.drawBitmap(0, 0, disp_buf, W, H); //отобразить на дисплеи
else
tft.drawBitmap(0, 0, disp_buf, W, V[V_level_Y_down] + 10); //отобразить на дисплеи часть изображения с запасом на 10 пикселей
heap_caps_free(disp_buf); //освободить буфер
return ESP_OK;
}
//---------------------------------------------------- dispalay_ttf_B_W
//---------------------------------------------------- sum_frames
esp_err_t sum_frames(uint16_t *fr_buf, bool show, uint8_t Y_up, uint8_t Y_down) {
uint32_t tstart;
fb = NULL;
//накопление кадров - проинтегрировать несколько кадров для устранения шумов
tstart = clock();
memset(fr_buf, 0, F_WIDTH * F_HEIGHT * 2); //выделить память и очистить размер в байтих
uint8_t frame_c = V[V_number_of_sum_frames];
if (s->pixformat != PIXFORMAT_GRAYSCALE)
frame_c = 1; //если цветное то не суммировать по кадрам
for (uint8_t frames = 0; frames < frame_c; frames++) { //усредненее по кадрам frame_count
if (fb) { //освободить буфер
esp_camera_fb_return(fb);
fb = NULL;
}
fb = esp_camera_fb_get(); //получить данные от камеры
if (!fb) {
Serial.printf("Camera capture failed to display\n");
return ESP_FAIL;
}
uint32_t i_max = fb->height * fb->width; //мксимальное значенее массива для данного экрана
for (uint16_t y = 0; y < F_HEIGHT; y++) { //работаем только с верхней частью кадра
for (uint16_t x = 0; x < fb->width; x++) {
if (s->pixformat == PIXFORMAT_GRAYSCALE) {
uint32_t i = ((y + V[V_offset_y]) * fb->width + x + V[V_offset_x]); //смещенее по оси Y и Х
uint32_t j = (y * fb->width + x); //GRAYSCALE
if ((y < Y_up) || (y > Y_down)) {
fr_buf[j] = 0; //обнулим значения ниже и выше Y - эммитация шторки
// Serial.printf("\n");
}
else {
if (i < i_max) //размер экрана (176)+смещенее может быть больше размера изображения 240
fr_buf[j] += fb->buf[i];
else
fr_buf[j] = 0;
}
}
else { //если RGB565 берем 8 битный буфер и преобразуем в 16 битный
uint32_t i = (y * fb->width + x) << 1; //если RGB565
uint32_t j = (y * fb->width + x); //если RGB565
//https://github.com/techtoys/SSD2805/blob/master/Microchip/Include/Graphics/gfxcolors.h
fr_buf[j] += (uint16_t)(fb->buf[i]) << 8 | (uint16_t)(fb->buf[i + 1]); //преобразуем в 16 битное
}
}
}
} //суммирование по кадрам
//усредним все пиксели
for (uint16_t i = 0; i < F_WIDTH * F_HEIGHT; i++) {
fr_buf[i] = (uint16_t)(fr_buf[i] / frame_c);
}
if (fb) { //освободить буфер
esp_camera_fb_return(fb);
fb = NULL;
}
if (show) {
Serial.printf("Capture camera time: %u ms of %d frames\n", clock() - tstart, frame_c);
}
return ESP_OK;
}
//---------------------------------------------------- sum_frames
//---------------------------------------------------- camera_capture
esp_err_t camera_capture(uint16_t *fr_buf, bool show, uint8_t Y_up, uint8_t Y_down) {
//сумировать кадры
if (sum_frames(fr_buf, show, Y_up, Y_down) != ESP_OK) return ESP_FAIL;
uint32_t tstart = clock();
if (show) {
dispalay_ttf_B_W(fr_buf, 0, 0);
Serial.printf("Send buffer time: %u ms\n", clock() - tstart);
}
}
//---------------------------------------------------- camera_capture
//---------------------------------------------------- setup
void setup() {
WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); //disable brownout detector
Serial.begin(115200);
Serial.setDebugOutput(false);
camera_config_t config;
config.ledc_channel = LEDC_CHANNEL_0;
config.ledc_timer = LEDC_TIMER_0;
config.pin_d0 = Y2_GPIO_NUM;
config.pin_d1 = Y3_GPIO_NUM;
config.pin_d2 = Y4_GPIO_NUM;
config.pin_d3 = Y5_GPIO_NUM;
config.pin_d4 = Y6_GPIO_NUM;
config.pin_d5 = Y7_GPIO_NUM;
config.pin_d6 = Y8_GPIO_NUM;
config.pin_d7 = Y9_GPIO_NUM;
config.pin_xclk = XCLK_GPIO_NUM;
config.pin_pclk = PCLK_GPIO_NUM;
config.pin_vsync = VSYNC_GPIO_NUM;
config.pin_href = HREF_GPIO_NUM;
config.pin_sscb_sda = SIOD_GPIO_NUM;
config.pin_sscb_scl = SIOC_GPIO_NUM;
config.pin_pwdn = PWDN_GPIO_NUM;
config.pin_reset = RESET_GPIO_NUM;
config.xclk_freq_hz = 20000000;
/*
config.pixel_format = PIXFORMAT_GRAYSCALE; //PIXFORMAT_JPEG;
//init with high specs to pre-allocate larger buffers
if (psramFound()) {
// config.frame_size = FRAMESIZE_UXGA;
config.frame_size = FRAMESIZE_QVGA;
config.jpeg_quality = 10;
config.fb_count = 1;
} else {
config.frame_size = FRAMESIZE_SCOLOR;
config.jpeg_quality = 12;
config.fb_count = 1;
}
// config.frame_size = FRAMESIZE_QVGA;
// Camera init
*/
// for display
config.frame_size = FRAMESIZE_QVGA;
config.pixel_format = PIXFORMAT_GRAYSCALE; //PIXFORMAT_GRAYSCALE; //PIXFORMAT_RGB565;
config.fb_count = 2;
esp_err_t err = esp_camera_init(&config);
if (err != ESP_OK) {
Serial.printf("Camera init failed with error 0x%x", err);
delay(1000);
ESP.restart();
}
//drop down frame size for higher initial frame rate
s = esp_camera_sensor_get();
s->set_framesize(s, FRAMESIZE_QVGA);
/*
#ifdef display_2_0
s->set_hmirror(s, 1);
s->set_vflip(s, 1);
#endif
*/
// s->set_special_effect(s, 1); //Negative
//s->set_brightness(s, Value);
// s->set_saturation(s, Value);
// s->set_contrast(s, Value);
// Wi-Fi connection
uint32_t timeout = millis();
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.printf(".");
if (millis() - timeout > 5000) break;
}
if (WiFi.status() == WL_CONNECTED) {
Serial.printf("\nWiFi connected.\nCamera Stream Ready! Go to: http://");
Serial.printf("%s\n", WiFi.localIP().toString().c_str());
}
else { //create AP
WiFi.softAP("ESP32", "87654321");
Serial.printf("\nWiFi %s not found create AP Name - 'ESP32' Password - '87654321'\n", ssid);
Serial.printf("Camera Stream Ready! Go to: http://");
Serial.printf("%s\n", WiFi.softAPIP().toString());
}
tft.begin();
tft.setOrientation(3);
tft.clear(); //черным
uint32_t f8 = heap_caps_get_free_size(MALLOC_CAP_8BIT);
frame_buf = (uint16_t *)heap_caps_calloc(F_WIDTH * F_HEIGHT * 2, 1, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
if (frame_buf == NULL) {
Serial.printf("malloc failed frame_buf\n");
}
else {
Serial.printf("malloc succeeded frame_buf\n");
}
Serial.printf("8BIT = %d\n", f8 - heap_caps_get_free_size(MALLOC_CAP_8BIT));
for (uint8_t dig = 0; dig < number_letter; dig++) {
Hemming[dig].dig_defined = 10; //заносим первоначально максимальное число вне диапазона 0-9
}
// Start web server
// startCameraServer();
virtuino.begin(onReceived, onRequested, 512); //Start Virtuino. Set the buffer to 256. With this buffer Virtuino can control about 28 pins (1 command = 9bytes) The T(text) commands with 20 characters need 20+6 bytes
//virtuino.key="1234"; //This is the Virtuino password. Only requests the start with this key are accepted from the library
// avoid special characters like ! $ = @ # % & * on your password. Use only numbers or text characters
server.begin();
/*
for (uint8_t dig = 0; dig < number_letter; dig++) {
for (uint8_t y = 0; y < height_letter; y++) { //перебор по Y
printBinary(original[dig][y], "\n");
}
}
*/
for (uint16_t i = 0; i < size_m3; i++) { //обнулим буфер сохранения значений
Gas[i].m3 = 0;
Gas[i].minutes = 0;
}
Gas[0].minutes = 1; //подсчет времени сначала для текущего элемента
Gas_minute_Ticker.attach(60, m3_calculate); //вызывать расчета объма газа каждую минуту 60
//init and get the time
configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
if (!getLocalTime(&timeinfo)) { //получим время начала записи сохранения м3
Serial.printf("Failed to obtain time\n");
return;
}
EEPROM.begin(16);//Установить размер внутренней памяти для хранения первоначальных значений
init_V();
}
//---------------------------------------------------- setup
//---------------------------------------------------- find_max_number
uint8_t find_max_number(uint8_t d) {
//возврат максимального значения из всех найденых
uint8_t n = 0; //первое значенее 0
uint8_t m1 = frequency[0][d]; //присваиваем первое значение
for (uint8_t i = 1; i < number_of_samples; i++) {
if (m1 < frequency[i][d]) {
m1 = frequency[i][d];
n = i;
}
}
return n;
}
//---------------------------------------------------- find_max_number
//---------------------------------------------------- show_result
void show_result(bool show) {
uint8_t defined;
uint16_t next_x = 0;
char buf[10]; //буфер для формирования строки расстояния Хемминга и частоты повторения цифр
T_0 = ""; //обновим строчку вывода результатов
int16_t w, h;
tft.setGFXFont(&FreeSansBold24pt7b); // Set current font
tft.getGFXTextExtent("0", 0, info_result, &w, &h); // Get string extents
h += info_result;
// Serial.printf("info_result=%d w=%d h=%d\n",info_result,w,h);
// tft.setFont(Trebuchet_MS16x21); //22 pixel for size 3 Trebuchet_MS16x21
tft.fillRectangle (0, info_result - 2, tft.maxX(), h + 7, COLOR_BLACK); //очистить часть экрана GFXFont привязан верхней точкой
//найти максимальную частоту вхождения цифр после опознавания
for (uint8_t dig = 0; dig < number_letter; dig++) { //number_letter
defined = find_max_number(dig);
sprintf(buf, "%d\0", defined);
//обновленее первоначально предопределенного набора символов если частота определения символа более 7 и рассояние Хемминга менее Hemming_level
if ((frequency[defined][dig] > average_count_level) && (Hemming[dig].min_Hemming < Hemming_level)) {
if (defined != Hemming[dig].dig_defined) { //корректно обнаружили первый раз
// Serial.printf("Change defined digital in position=%d from=%d to %d\n", dig, Hemming[dig].dig_defined, defined);
Hemming[dig].dig_defined = defined;
tft.drawGFXText(next_x, h, buf, COLOR_YELLOW); // Print string
// tft.drawText(next_x, info_result,buf,COLOR_YELLOW); //цифра опознана с большой вероятностью
}
else tft.drawGFXText(next_x, h, buf, COLOR_GREEN); //цифра распознана корректно уже неоднократно
}
else tft.drawGFXText(next_x, h, buf, COLOR_RED);
T_0 += defined;
next_x += w; //шаг между цифрами
if (dig == 4) {
T_0 += ".";
tft.drawGFXText(next_x, h, ".", COLOR_GREEN);
next_x += (w >> 1); //шаг между цифрами
}
Hemming[dig].result = defined;
Hemming[dig].frequency = frequency[defined][dig];
if ((Hemming[dig].frequency < average_count_level) && (Hemming[dig].min_Hemming > Hemming_level)) {
Hemming[dig].dig_defined = 10; //не распознали с большой вероятностью
}
if (show)
Serial.printf("found number=%2d frequency=%2d Hemming_min=%4d Hemming_defined =%d position=%2d Hemming_next=%4d next_dig=%2d delta=%3d x_width=%d\n",
Hemming[dig].result, Hemming[dig].frequency, Hemming[dig].min_Hemming, Hemming[dig].dig_defined, Hemming[dig].etalon_number,
Hemming[dig].next_min_Hemming, Hemming[dig].next_result, Hemming[dig].next_min_Hemming - Hemming[dig].min_Hemming, Hemming[dig].x_width);
}
if (show) Serial.printf("\n");
//сохраненее данных для вывода на экран Virtuino
V[V_D0] = Hemming[0].dig_defined;
V[V_D1] = Hemming[1].dig_defined;
V[V_D2] = Hemming[2].dig_defined;
V[V_D3] = Hemming[3].dig_defined;
V[V_D4] = Hemming[4].dig_defined;
V[V_D5] = Hemming[5].dig_defined;
V[V_D6] = Hemming[6].dig_defined;
V[V_D7] = Hemming[7].dig_defined;
//вывод растояния Хемминга
T_1 = "";
T_2 = "";
tft.setFont(Terminal6x8); //10 pixel
tft.fillRectangle (0, info_Hemming - 2, tft.maxX(), info_Hemming + 24, COLOR_BLACK); //очистить часть экрана для расстояния Хеминга и частоты
for (uint8_t dig = 0; dig < number_letter; dig++) {
sprintf(buf, "|%3d \0", Hemming[dig].min_Hemming);
next_x = max(tft.getTextWidth(T_1), tft.getTextWidth(T_2));
if (next_x != 0) next_x -= tft.getTextWidth(" ");
if (Hemming[dig].min_Hemming < Hemming_level)
tft.drawText(next_x, info_Hemming, buf, COLOR_GREEN); //печатать каждую цифру со смещенеем на экране дисплея
else
tft.drawText(next_x, info_Hemming, buf, COLOR_RED); //печатать каждую цифру со смещенеем на экране дисплея
// Serial.printf("%3d %3d '%s'\n",next_x,tft.getTextWidth(T_1),T_1.c_str());
T_1 += buf;
sprintf(buf, "|%3d \0", Hemming[dig].frequency);
if (Hemming[dig].frequency > average_count_level)
tft.drawText(next_x, info_frequency, buf, COLOR_GREEN); //печатать каждую цифру со смещенеем на экране дисплея
else
tft.drawText(next_x, info_frequency, buf, COLOR_RED); //печатать каждую цифру со смещенеем на экране дисплея
// Serial.printf("%3d %3d %3d '%s'\n",next_x,tft.getTextWidth(T_2),tft.getTextWidth(T_2)-5,T_2.c_str());
T_2 += buf;
}
T_1 += "|";
T_2 += "|";
// Serial.printf("T_1 = %s\n",T_2.c_str());
}
//---------------------------------------------------- show_result
uint32_t free_heap;
bool V_GBW_old = false;
//---------------------------------------------------- loop
void loop() {
for (uint8_t dig = 0; dig < number_letter; dig++) { //обнулить массив для поиска частоты повторения цифр
for (uint8_t i = 0; i < number_of_samples; i++) { //перебор по всем значения образцов
frequency[i][dig] = 0;
}
}
for (uint8_t count = 0; count < average_count; count++) { //повторим результат и найдем опознаные числа
//обработка запросов web сервера
virtuinoRun(); // Necessary function to communicate with Virtuino. Client handler
if (V[V_SH_M3] == 1) print_m3(); //вывести накопленные даные на экран монитора
if (V[V_GBW] == 2) { //Вывод полного экрана без анализа
camera_capture(frame_buf, false, 0, F_HEIGHT); //получить кадры с камеры и усреднить их
dispalay_ttf_B_W(frame_buf, pixel_level, V[V_level_dispalay_ttf_B_W]); //повысим на 5-20 единиц, чтобы убрать засветку
V_GBW_old = true;
}
else {
if (V_GBW_old) tft.clear(); //очистка после вывода полного экрана без анализа
V_GBW_old = false;
if(V[V_GBW] == 1)
camera_capture(frame_buf, false, V[V_level_Y_up]-10, V[V_level_Y_down]+10); //получить кадры с камеры и усреднить их
else
camera_capture(frame_buf, false, V[V_level_Y_up], V[V_level_Y_down]); //получить кадры с камеры и усреднить их
//найти средний уровень пикселей окна табло
pixel_level = find_middle_level_image(frame_buf, false);
//отображение на дисплеи
dispalay_ttf_B_W(frame_buf, pixel_level, V[V_level_dispalay_ttf_B_W]); //повысим на 5-20 единиц, чтобы убрать засветку
// free_heap = heap_caps_get_free_size(MALLOC_CAP_8BIT);
//поиск положения окна цифр - при найденом уровне по оси y
find_digits_y(frame_buf, pixel_level, V[V_level_find_digital_Y], false); //уровень повысим на 15 единиц, чтобы убрать засветку
// Serial.printf("heap = %d\n",free_heap-heap_caps_get_free_size(MALLOC_CAP_8BIT));
//поиск максимума - предположительно середины цифр
find_max_digital_X(frame_buf, pixel_level, V[V_level_find_digital_X], false); //уровень повысим на 7 единиц, чтобы убрать засветку
//найти средний уровень для каждой цифры
find_middle_britnes_digital(frame_buf, false);
//преобразование в 32 битное числа
convert_to_32(frame_buf, pixel_level, V[V_level_convert_to_32], false); //уровень повысим на 20 единиц, чтобы убрать засветку
//сравнить с эталонном - рассчет расстояния Хемминга
for (uint8_t dig = 0; dig < number_letter; dig++) { //проврека по всем цифрам шкалы
result[count][dig] = image_recognition(dig, V[V_show_digital]);
frequency[result[count][dig]][dig]++; //посчет числа совпадения цифра определенной цифры
}
// if ((int)(millis() - 20000) > 0) //ждать стабилизации камеры 20 секунд
// show_result(false); //вывести результат, чтобы результат всегда был на экране
}
}
if (V[V_SH_M3] == 1) print_m3(); //вывести накопленные даные на экран монитора
if (V[V_GBW] != 2) {
// if ((int)(millis() - 20000) > 0) //ждать стабилизации камеры 20 секунд
show_result(true);
}
change_variables(false); //если были изменения коэффициентов записать
}
//---------------------------------------------------- loop
файл sample.h с эталонами
#define sample_height height_letter
#define number_of_samples 41//число эталонов
//массив соответсвий образцов и цифр
uint8_t sample_equation[] = { 0,0,0,1,1,1,2,2,2,2,3,3,3,4,4,4,5,5,5,6,6,6,6,7,7,7,8,8,8,9,9,9,9,9,0,1,
0,1,2,4,6,}; //старый счетчик
static uint32_t sample[][sample_height] = {
{//0 (0)
0x0000000000, //
0x0000003c00, // 1111
0x000000fe00, // 1111111
0x000000ff00, // 11111111
0x000000ff80, // 111111111
0x000000e780, // 111 1111
0x000001e780, // 1111 1111
0x000001e380, // 1111 111
0x000001e380, // 1111 111
0x000001e380, // 1111 111
0x000001c3c0, // 111 1111
0x000001c3c0, // 111 1111
0x000001c3c0, // 111 1111
0x000001c3c0, // 111 1111
0x000001e3c0, // 1111 1111
0x000001e380, // 1111 111
0x000001e380, // 1111 111
0x000001e380, // 1111 111
0x000000e780, // 111 1111
0x000000e380, // 111 111
0x000000ff80, // 111111111
0x000000ff00, // 11111111
0x0000003c00, // 1111
0x0000000000, //
0x0000000000, //
0x0000000000, //
},
{//1 (0)
0x0000000000, //
0x0000004200, // 1 1
0x000001c380, // 111 111
0x000001c380, // 111 111
0x000003c3c0, // 1111 1111
0x000003c3c0, // 1111 1111
0x000003c3c0, // 1111 1111
0x000003c3c0, // 1111 1111
0x000003c3c0, // 1111 1111
0x000003c3c0, // 1111 1111
0x000003c3c0, // 1111 1111
0x000003c780, // 1111 1111
0x000003c780, // 1111 1111
0x000001e780, // 1111 1111
0x000001ff80, // 1111111111
0x000001ff80, // 1111111111
0x000000ff00, // 11111111
0x0000007e00, // 111111
0x0000000000, //
0x0000000000, //
0x0000000000, //
0x0000000000, //
0x0000001c00, // 111
0x0000003c00, // 1111
0x0000003c00, // 1111
0x0000003c00, // 1111
},
{//2 (0)
0x0000000000, //
0x0000010380, // 1 111
0x0000010380, // 1 111
0x0000018380, // 11 111
0x0000038380, // 111 111
0x000003c3c0, // 1111 1111
0x000003c3c0, // 1111 1111
0x000003c380, // 1111 111
0x000003c380, // 1111 111
0x000001c380, // 111 111
0x000001c780, // 111 1111
0x000001c780, // 111 1111
0x000001ff80, // 1111111111
0x000000ff00, // 11111111
0x000000ff00, // 11111111
0x0000007e00, // 111111
0x0000000000, //
0x0000000000, //
0x0000000000, //
0x0000000000, //
0x0000001800, // 11
0x0000003800, // 111
0x0000003800, // 111
0x0000003800, // 111
0x0000003800, // 111
0x0000000000, //
},
{//3 (1)
0x0000000000, //
0x0000000000, //
0x0000003c00, // 1111
0x0000003c00, // 1111
0x0000003c00, // 1111
0x0000003c00, // 1111
0x0000003c00, // 1111
0x0000003c00, // 1111
0x0000003c00, // 1111
0x0000003c00, // 1111
0x0000003c00, // 1111
0x0000003c00, // 1111
0x0000003c00, // 1111
0x0000003c00, // 1111
0x0000003c00, // 1111
0x0000003c00, // 1111
0x0000003c00, // 1111
0x0000003c00, // 1111
0x0000003c00, // 1111
0x0000003c00, // 1111
0x0000001c00, // 111
0x0000001c00, // 111
0x0000001c00, // 111
0x0000001c00, // 111
0x0000000000, //
0x0000000000, //
},
{//4 (1)
0x0000007f00, // 1111111
0x0000003e00, // 11111
0x0000000000, //
0x0000000000, //
0x0000000000, //
0x0000001000, // 1
0x0000001c00, // 111
0x0000001c00, // 111
0x0000003c00, // 1111
0x0000003c00, // 1111
0x0000003c00, // 1111
0x0000003c00, // 1111
0x0000003c00, // 1111
0x0000003c00, // 1111
0x0000003c00, // 1111
0x0000003c00, // 1111
0x0000003c00, // 1111
0x0000003c00, // 1111
0x0000003c00, // 1111
0x0000003c00, // 1111
0x0000003c00, // 1111
0x0000003c00, // 1111
0x0000001c00, // 111
0x0000001c00, // 111
0x0000001c00, // 111
0x0000000000, //
},
{//5 (1)
0x0000000000, //
0x0000000000, //
0x0000001800, // 11
0x0000001c00, // 111
0x0000001c00, // 111
0x0000003c00, // 1111
0x0000003c00, // 1111
0x0000003c00, // 1111
0x0000003c00, // 1111
0x0000003c00, // 1111
0x0000003c00, // 1111
0x0000003c00, // 1111
0x0000003c00, // 1111
0x0000003c00, // 1111
0x0000003c00, // 1111
0x0000003c00, // 1111
0x0000003c00, // 1111
0x0000003c00, // 1111
0x0000003c00, // 1111
0x0000001c00, // 111
0x0000001c00, // 111
0x0000001c00, // 111
0x0000001c00, // 111
0x0000000000, //
0x0000000000, //
0x0000000000, //
},
{//6 (2)
0x0000000000, //
0x0000000000, //
0x0000000000, //
0x0000003c00, // 1111
0x0000007f00, // 1111111
0x000000ff80, // 111111111
0x000001ffc0, // 11111111111
0x000001e3c0, // 1111 1111
0x000003c3c0, // 1111 1111
0x000001c3c0, // 111 1111
0x00000007c0, // 11111
0x0000000780, // 1111
0x0000000f80, // 11111
0x0000001f00, // 11111
0x0000001e00, // 1111
0x0000003e00, // 11111
0x0000003c00, // 1111
0x0000007800, // 1111
0x0000007800, // 1111
0x000000f000, // 1111
0x000000f000, // 1111
0x000000f000, // 1111
0x000000ffc0, // 1111111111
0x000000ffe0, // 11111111111
0x000001ffe0, // 111111111111
0x0000000000, //
},
{//7 (2)
0x0000000000, //
0x0000000000, //
0x0000003e00, // 11111
0x000000ff00, // 11111111
0x000001ff80, // 1111111111
0x000003ffc0, // 111111111111
0x000007c7e0, // 11111 111111
0x000007c3e0, // 11111 11111
0x000007c3e0, // 11111 11111
0x00000387e0, // 111 111111
0x0000000fc0, // 111111
0x0000000f80, // 11111
0x0000001f80, // 111111
0x0000003f00, // 111111
0x0000007e00, // 111111
0x0000007c00, // 11111
0x000000fc00, // 111111
0x000000f800, // 11111
0x000001f800, // 111111
0x000001f000, // 11111
0x000001e000, // 1111
0x000003e000, // 11111
0x000003ff80, // 11111111111
0x000003ffe0, // 1111111111111
0x000003ffe0, // 1111111111111
0x0000000000, //
},
{//8 (2)
0x0000000000, //
0x0000000000, //
0x0000000000, //
0x0000000000, //
0x0000007800, // 1111
0x000001fe00, // 11111111
0x000003ff00, // 1111111111
0x000007ff80, // 111111111111
0x0000078780, // 1111 1111
0x00000f8780, // 11111 1111
0x0000070780, // 111 1111
0x0000020780, // 1 1111
0x0000000f80, // 11111
0x0000001f00, // 11111
0x0000003f00, // 111111
0x0000007e00, // 111111
0x000000fc00, // 111111
0x000000fc00, // 111111
0x000000f800, // 11111
0x000001f000, // 11111
0x000003f000, // 111111
0x000003e000, // 11111
0x000003e000, // 11111
0x000003e000, // 11111
0x000007ffc0, // 1111111111111
0x000007ffc0, // 1111111111111
},
{//9 (2)
0x0000000000, //
0x0000000c00, // 11
0x0000000800, // 1
0x0000000000, //
0x0000000000, //
0x0000000000, //
0x0000000000, //
0x0000007f00, // 1111111
0x000000ff80, // 111111111
0x000001ff80, // 1111111111
0x000001c380, // 111 111
0x000001c380, // 111 111
0x000003c380, // 1111 111
0x0000000780, // 1111
0x0000000f00, // 1111
0x0000000f00, // 1111
0x0000001e00, // 1111
0x0000003e00, // 11111
0x0000003c00, // 1111
0x0000007800, // 1111
0x0000007000, // 111
0x000000f000, // 1111
0x000000f000, // 1111
0x000000e000, // 111
0x000001e000, // 1111
0x000001e000, // 1111
},
{//10 (3)
0x0000000000, //
0x0000000000, //
0x000003fe00, // 111111111
0x000003fe00, // 111111111
0x000003ff00, // 1111111111
0x0000001f00, // 11111
0x0000001f00, // 11111
0x0000001e00, // 1111
0x0000003e00, // 11111
0x0000003c00, // 1111
0x0000007c00, // 11111
0x000000fc00, // 111111
0x000000fe00, // 1111111
0x000000ff00, // 11111111
0x0000007f80, // 11111111
0x0000000780, // 1111
0x0000000780, // 1111
0x0000000780, // 1111
0x0000000780, // 1111
0x0000000780, // 1111
0x0000000780, // 1111
0x000003ff80, // 11111111111
0x000003ff00, // 1111111111
0x000003fe00, // 111111111
0x0000000000, //
0x0000000000, //
},
{//11 (3)
0x0000000000, //
0x000001fc00, // 1111111
0x000001fe00, // 11111111
0x000001fe00, // 11111111
0x0000001e00, // 1111
0x0000001e00, // 1111
0x0000001e00, // 1111
0x0000003c00, // 1111
0x0000003c00, // 1111
0x0000007800, // 1111
0x000000f800, // 11111
0x000000fc00, // 111111
0x000000fe00, // 1111111
0x000000ff00, // 11111111
0x0000000f00, // 1111
0x0000000700, // 111
0x0000000700, // 111
0x0000000700, // 111
0x0000000700, // 111
0x0000000700, // 111
0x0000000f00, // 1111
0x000003ff00, // 1111111111
0x000003fe00, // 111111111
0x0000000000, //
0x0000000000, //
0x0000000000, //
},
{//12 (3)
0x0000000200, // 1
0x0000000300, // 11
0x0000000380, // 111
0x0000000380, // 111
0x0000000380, // 111
0x0000000380, // 111
0x0000000380, // 111
0x0000000380, // 111
0x0000000780, // 1111
0x000003ff00, // 1111111111
0x000003fe00, // 111111111
0x000003fc01, // 11111111 1
0x0000000000, //
0x0000000000, //
0x0000000000, //
0x0000000000, //
0x0000000301, // 11 1
0x0000000700, // 111
0x0000000f00, // 1111
0x0000001e00, // 1111
0x0000001c00, // 111
0x0000007800, // 1111
0x0000007000, // 111
0x0000007000, // 111
0x000000e000, // 111
0x0000000000, //
},
{//13 (4)
0x0000000000, //
0x0000000000, //
0x0000001c00, // 111
0x0000003c00, // 1111
0x0000003c00, // 1111
0x0000003800, // 111
0x0000007800, // 1111
0x0000007800, // 1111
0x0000007800, // 1111
0x000000f000, // 1111
0x000000f000, // 1111
0x000000f000, // 1111
0x000000e000, // 111
0x000001e600, // 1111 11
0x000001ef00, // 1111 1111
0x000001ef00, // 1111 1111
0x000003ff80, // 11111111111
0x000003ff80, // 11111111111
0x000003ff80, // 11111111111
0x000001ff00, // 111111111
0x0000000f00, // 1111
0x0000000e00, // 111
0x0000000e00, // 111
0x0000000000, //
0x0000000000, //
0x0000000000, //
},
{//14 (4)
0x0000000000, //
0x0000000000, //
0x0000001c00, // 111
0x0000001c00, // 111
0x0000003c00, // 1111
0x0000003c00, // 1111
0x0000007800, // 1111
0x0000007800, // 1111
0x0000007000, // 111
0x000000f000, // 1111
0x000000f000, // 1111
0x000000f000, // 1111
0x000001e200, // 1111 1
0x000001ef00, // 1111 1111
0x000001ef00, // 1111 1111
0x000003ef00, // 11111 1111
0x000003ff80, // 11111111111
0x000003ff80, // 11111111111
0x000003ff80, // 11111111111
0x000003ff80, // 11111111111
0x0000000f00, // 1111
0x0000000f00, // 1111
0x0000000f00, // 1111
0x0000000000, //
0x0000000000, //
0x0000000000, //
},
{//15 (4)
0x0000000000, //
0x0000008000, // 1
0x0000008000, // 1
0x000000c700, // 11 111
0x000001e700, // 1111 111
0x000001ff80, // 1111111111
0x000001ff80, // 1111111111
0x000001ff80, // 1111111111
0x0000000700, // 111
0x0000000700, // 111
0x0000000700, // 111
0x0000000700, // 111
0x0000000000, //
0x0000000000, //
0x0000000000, //
0x0000000000, //
0x000001ff01, // 111111111 1
0x000001ff80, // 1111111111
0x000001ff00, // 111111111
0x000001c000, // 111
0x000001c000, // 111
0x000001c000, // 111
0x0000018000, // 11
0x000001c000, // 111
0x000001e000, // 1111
0x0000000000, //
},
{//16 (5)
0x0000000000, //
0x0000010100, // 1 1
0x0000011100, // 1 1 1
0x0000000000, //
0x0000000000, //
0x0000000600, // 11
0x0000000000, //
0x0000000000, //
0x0000000000, //
0x0000000000, //
0x0000000000, //
0x000001ff00, // 111111111
0x000001ff00, // 111111111
0x000001ff00, // 111111111
0x000001c000, // 111
0x000001c000, // 111
0x000001c001, // 111 1
0x000001c000, // 111
0x000001c000, // 111
0x000001f800, // 111111
0x000001fe00, // 11111111
0x000001ff0f, // 111111111 1111
0x000000070e, // 111 111
0x0000000700, // 111
0x0000000380, // 111
0x0000000000, //
},
{//17 (5)
0x0000000000, //
0x0000000000, //
0x000001ff80, // 1111111111
0x000001ff80, // 1111111111
0x000001ff00, // 111111111
0x000001e000, // 1111
0x000001c000, // 111
0x000001c000, // 111
0x000001e000, // 1111
0x000001fc00, // 1111111
0x000001fe00, // 11111111
0x000001ff00, // 111111111
0x000000ff80, // 111111111
0x0000000780, // 1111
0x0000000380, // 111
0x0000000380, // 111
0x0000000380, // 111
0x0000000380, // 111
0x0000000780, // 1111
0x0000000780, // 1111
0x000001ff00, // 111111111
0x000001fe00, // 11111111
0x000001fc00, // 1111111
0x0000000000, //
0x0000000000, //
0x0000000000, //
},
{//18 (5)
0x0000000000, //
0x0000000000, //
0x000000ff80, // 111111111
0x000001ff80, // 1111111111
0x000001ff00, // 111111111
0x000001c000, // 111
0x000001c000, // 111
0x000001c000, // 111
0x000001c000, // 111
0x000001e000, // 1111
0x000001fc00, // 1111111
0x000001fe00, // 11111111
0x000001ff00, // 111111111
0x000000ff80, // 111111111
0x0000000780, // 1111
0x0000000780, // 1111
0x0000000780, // 1111
0x0000000780, // 1111
0x0000000780, // 1111
0x0000000780, // 1111
0x0000000780, // 1111
0x000001ff00, // 111111111
0x000001ff00, // 111111111
0x000001fe00, // 11111111
0x0000000000, //
0x0000000000, //
},
{//19 (6)
0x0000000000, //
0x0000000000, //
0x0000000700, // 111
0x0000000f00, // 1111
0x0000001e00, // 1111
0x0000003c00, // 1111
0x0000007c00, // 11111
0x0000007800, // 1111
0x0000007000, // 111
0x000000f000, // 1111
0x000000fc00, // 111111
0x000001fe00, // 11111111
0x000001ff00, // 111111111
0x000001ff80, // 1111111111
0x000001e780, // 1111 1111
0x000001c380, // 111 111
0x000001c380, // 111 111
0x000001c380, // 111 111
0x000001c380, // 111 111
0x000001e780, // 1111 1111
0x000001ff00, // 111111111
0x000000fe00, // 1111111
0x0000007c00, // 11111
0x0000000000, //
0x0000000000, //
0x0000000000, //
},
{//20 (6)
0x0000000000, //
0x0000000000, //
0x0000000700, // 111
0x0000000f00, // 1111
0x0000001e00, // 1111
0x0000003c00, // 1111
0x0000003800, // 111
0x0000007800, // 1111
0x0000007000, // 111
0x000000f000, // 1111
0x000000f000, // 1111
0x000001fe00, // 11111111
0x000001ff00, // 111111111
0x000001ff00, // 111111111
0x000003f780, // 111111 1111
0x000003c380, // 1111 111
0x000003c380, // 1111 111
0x000003c380, // 1111 111
0x000003c380, // 1111 111
0x000003c380, // 1111 111
0x000001e780, // 1111 1111
0x000001ff00, // 111111111
0x000000fe00, // 1111111
0x0000007c00, // 11111
0x0000000000, //
0x0000000000, //
},
{//21 (6)
0x0000000000, //
0x0000000000, //
0x0000000700, // 111
0x0000000f00, // 1111
0x0000001e00, // 1111
0x0000003c00, // 1111
0x0000003800, // 111
0x0000007800, // 1111
0x0000007000, // 111
0x000000f000, // 1111
0x000000f800, // 11111
0x000001fe00, // 11111111
0x000001ff00, // 111111111
0x000001ff00, // 111111111
0x000003ff80, // 11111111111
0x000003c380, // 1111 111
0x000003c380, // 1111 111
0x000003c380, // 1111 111
0x000003c380, // 1111 111
0x000003c380, // 1111 111
0x000001e780, // 1111 1111
0x000001ff00, // 111111111
0x000000fe00, // 1111111
0x0000000000, //
0x0000000000, //
0x0000000000, //
},
{//22 (6)
0x0000000000, //
0x0000004000, // 1
0x0000004000, // 1
0x0000008000, // 1
0x000000f000, // 1111
0x000001fe00, // 11111111
0x000001ff80, // 1111111111
0x000001e780, // 1111 1111
0x000001c380, // 111 111
0x000003c380, // 1111 111
0x000003c380, // 1111 111
0x000003c380, // 1111 111
0x000003c380, // 1111 111
0x000001e780, // 1111 1111
0x000001ff00, // 111111111
0x000000fe00, // 1111111
0x0000007e01, // 111111 1
0x0000000000, //
0x0000000000, //
0x0000000000, //
0x0000000000, //
0x000001ff00, // 111111111
0x000001ff80, // 1111111111
0x000001ff00, // 111111111
0x0000000100, // 1
0x0000000000, //
},
{//23 (7)
0x0000000000, //
0x000003ff00, // 1111111111
0x000007ff80, // 111111111111
0x000007ff80, // 111111111111
0x000003ff00, // 1111111111
0x0000001e00, // 1111
0x0000001e00, // 1111
0x0000003e00, // 11111
0x0000003c00, // 1111
0x0000003800, // 111
0x0000007800, // 1111
0x0000007800, // 1111
0x000000f800, // 11111
0x000000f000, // 1111
0x000000f000, // 1111
0x000000f000, // 1111
0x000000f000, // 1111
0x000000f000, // 1111
0x000000f000, // 1111
0x000000f000, // 1111
0x000001e000, // 1111
0x000001e000, // 1111
0x000000e000, // 111
0x0000000000, //
0x0000000000, //
0x0000000000, //
},
{//24 (7)
0x0000000000, //
0x000000ff80, // 111111111
0x000003ff80, // 11111111111
0x000003ff80, // 11111111111
0x0000000f00, // 1111
0x0000000e00, // 111
0x0000001e00, // 1111
0x0000001c00, // 111
0x0000001c00, // 111
0x0000003800, // 111
0x0000003800, // 111
0x0000007800, // 1111
0x0000007800, // 1111
0x0000007000, // 111
0x000000f000, // 1111
0x000000f000, // 1111
0x000000f000, // 1111
0x000000f000, // 1111
0x000000e000, // 111
0x000000e000, // 111
0x000000e000, // 111
0x000000e000, // 111
0x000000e000, // 111
0x000000e000, // 111
0x0000000000, //
0x0000000000, //
},
{//25 (7)
0x0000000f00, // 1111
0x0000000e00, // 111
0x0000001e00, // 1111
0x0000001c00, // 111
0x0000003c00, // 1111
0x0000003c00, // 1111
0x0000003800, // 111
0x0000007800, // 1111
0x0000007800, // 1111
0x0000007000, // 111
0x0000007000, // 111
0x000000f000, // 1111
0x000000f000, // 1111
0x000000f000, // 1111
0x000000f000, // 1111
0x000000f000, // 1111
0x000000f000, // 1111
0x000000f000, // 1111
0x000000e000, // 111
0x0000000000, //
0x0000000000, //
0x0000000000, //
0x0000007e00, // 111111
0x000000ff00, // 11111111
0x000001ff00, // 111111111
0x000001ff00, // 111111111
},
{//26 (8)
0x0000004000, // 1
0x0000004000, // 1
0x0000006000, // 11
0x0000006000, // 11
0x0000006000, // 11
0x0000006000, // 11
0x0000000000, //
0x0000000000, //
0x0000000000, //
0x0000000000, //
0x0000000000, //
0x0000007e00, // 111111
0x0000007f00, // 1111111
0x000000ff80, // 111111111
0x000000e780, // 111 1111
0x000001e780, // 1111 1111
0x000001e781, // 1111 1111 1
0x000001e380, // 1111 111
0x000001e700, // 1111 111
0x000000ff00, // 11111111
0x000000ff00, // 11111111
0x000000ff00, // 11111111
0x000001ff80, // 1111111111
0x000001c380, // 111 111
0x0000018300, // 11 11
0x0000000000, //
},
{//27 (8)
0x0000000000, //
0x0000007c00, // 11111
0x000000fe00, // 1111111
0x000001fe00, // 11111111
0x000001cf00, // 111 1111
0x000001cf00, // 111 1111
0x000001cf00, // 111 1111
0x000001ff00, // 111111111
0x000001fe00, // 11111111
0x000001fe00, // 11111111
0x000001ff00, // 111111111
0x000001ff00, // 111111111
0x000001ef80, // 1111 11111
0x000003c780, // 1111 1111
0x000003c780, // 1111 1111
0x000003c780, // 1111 1111
0x000003c780, // 1111 1111
0x000003c780, // 1111 1111
0x000003e780, // 11111 1111
0x000001ff00, // 111111111
0x000000ff00, // 11111111
0x0000007e00, // 111111
0x0000000000, //
0x0000000000, //
0x0000000000, //
0x0000000000, //
},
{//28 (8)
0x0000000000, //
0x0000000000, //
0x0000001e00, // 1111
0x0000003f00, // 111111
0x0000007f00, // 1111111
0x000000f780, // 1111 1111
0x000000e780, // 111 1111
0x000000e780, // 111 1111
0x000000e780, // 111 1111
0x000000ff80, // 111111111
0x000000ff00, // 11111111
0x000000ff00, // 11111111
0x000000ff80, // 111111111
0x000000ff80, // 111111111
0x000001e7c0, // 1111 11111
0x000001e3c0, // 1111 1111
0x000001c3c0, // 111 1111
0x000001c3c0, // 111 1111
0x000001c3c0, // 111 1111
0x000001e3c0, // 1111 1111
0x000001e780, // 1111 1111
0x000001ff80, // 1111111111
0x000000ff00, // 11111111
0x0000007e00, // 111111
0x0000000000, //
0x0000000000, //
},
{//29 (9)
0x0000000000, //
0x0000010080, // 1 1
0x0000008080, // 1 1
0x0000014080, // 1 1 1
0x000000e380, // 111 111
0x000000ff80, // 111111111
0x000000ff00, // 11111111
0x0000001e00, // 1111
0x0000000000, //
0x0000000000, //
0x0000000000, //
0x0000000000, //
0x0000000000, //
0x0000000000, //
0x000000fc00, // 111111
0x000000ff00, // 11111111
0x000001ff80, // 1111111111
0x000001c780, // 111 1111
0x0000038380, // 111 111
0x0000038381, // 111 111 1
0x0000038380, // 111 111
0x0000038380, // 111 111
0x0000038380, // 111 111
0x000001cf00, // 111 1111
0x000001ff00, // 111111111
0x000000ff00, // 11111111
},
{//30 (9)
0x0000000000, //
0x0000000000, //
0x0000007c00, // 11111
0x000000ff00, // 11111111
0x000001ff00, // 111111111
0x000001ef80, // 1111 11111
0x000003c780, // 1111 1111
0x000003c780, // 1111 1111
0x000003c780, // 1111 1111
0x000003c780, // 1111 1111
0x000003c780, // 1111 1111
0x000001ff80, // 1111111111
0x000001ff00, // 111111111
0x000000ff00, // 11111111
0x0000007f00, // 1111111
0x0000001e00, // 1111
0x0000001e00, // 1111
0x0000001e00, // 1111
0x0000003c00, // 1111
0x0000007c00, // 11111
0x0000007800, // 1111
0x000000f000, // 1111
0x000000e000, // 111
0x000001c000, // 111
0x0000000000, //
0x0000000000, //
},
{//31 (9)
0x0000000000, //
0x0000000000, //
0x0000000000, //
0x0000007e00, // 111111
0x0000007f80, // 11111111
0x000000ff80, // 111111111
0x000001e7c0, // 1111 11111
0x000001c3c0, // 111 1111
0x000003c3c0, // 1111 1111
0x000003c3c0, // 1111 1111
0x000003c3c0, // 1111 1111
0x000001e7c0, // 1111 11111
0x000001ffc0, // 11111111111
0x000001ff80, // 1111111111
0x000000ff80, // 111111111
0x0000007f80, // 11111111
0x0000000f00, // 1111
0x0000000f00, // 1111
0x0000001e00, // 1111
0x0000001e00, // 1111
0x0000003c00, // 1111
0x0000003c00, // 1111
0x0000007800, // 1111
0x0000000000, //
0x0000000000, //
0x0000000000, //
},
{//32 (9)
0x0000000000, //
0x0000010100, // 1 1
0x0000010000, // 1
0x0000018100, // 11 1
0x0000018380, // 11 111
0x000001c700, // 111 111
0x000001ff80, // 1111111111
0x000001ff0f, // 111111111 1111
0x0000007f00, // 1111111
0x0000000f00, // 1111
0x0000000f00, // 1111
0x0000000e00, // 111
0x0000001e01, // 1111 1
0x0000003e00, // 11111
0x0000003c00, // 1111
0x0000007800, // 1111
0x000000f000, // 1111
0x000001e000, // 1111
0x000001e001, // 1111 1
0x0000008000, // 1
0x0000000000, //
0x0000000000, //
0x0000000000, //
0x0000000000, //
0x0000007c01, // 11111 1
0x000001fe00, // 11111111
},
{//33 (9)
0x0000000000, //
0x0000010000, // 1
0x0000010100, // 1 1
0x000001e700, // 1111 111
0x000000ff00, // 11111111
0x0000007f00, // 1111111
0x0000000f00, // 1111
0x0000000700, // 111
0x0000000e00, // 111
0x0000001c00, // 111
0x0000001c00, // 111
0x0000003c00, // 1111
0x0000007800, // 1111
0x000000f000, // 1111
0x000000e000, // 111
0x000001c001, // 111 1
0x0000000000, //
0x0000000000, //
0x0000000000, //
0x0000000000, //
0x0000000000, //
0x0000007c00, // 11111
0x000000fe00, // 1111111
0x0000018600, // 11 11
0x0000010100, // 1 1
0x0000000000, //
},
//старый счетчик
{//34 (0)
0x0000000000, //
0x0000000000, //
0x000000ff00, // 11111111
0x000003ff80, // 11111111111
0x000007ffc0, // 1111111111111
0x00000781c0, // 1111 111
0x00000701e0, // 111 1111
0x00000701e0, // 111 1111
0x00000f00e0, // 1111 111
0x00000f00e0, // 1111 111
0x00000f00e0, // 1111 111
0x00000f00e0, // 1111 111
0x00000f00e0, // 1111 111
0x00000700e0, // 111 111
0x00000700e0, // 111 111
0x00000700e0, // 111 111
0x00000700e0, // 111 111
0x00000700e0, // 111 111
0x00000700e0, // 111 111
0x00000781e0, // 1111 1111
0x00000781e0, // 1111 1111
0x000007ffe0, // 11111111111111
0x000001ffc0, // 11111111111
0x000001ff00, // 111111111
0x0000000000, //
0x0000000000, //
},
{//35 (1)
0x0000000000, //
0x0000001e00, // 1111
0x0000003f00, // 111111
0x0000007f00, // 1111111
0x0000007f00, // 1111111
0x000000ff00, // 11111111
0x000000ff00, // 11111111
0x000000ff00, // 11111111
0x000000cf00, // 11 1111
0x0000000f00, // 1111
0x0000000f00, // 1111
0x0000000f00, // 1111
0x0000000f00, // 1111
0x0000000f00, // 1111
0x0000000f00, // 1111
0x0000000f00, // 1111
0x0000000f00, // 1111
0x0000000f00, // 1111
0x0000000f00, // 1111
0x0000000f00, // 1111
0x0000000f00, // 1111
0x0000000f00, // 1111
0x0000000700, // 111
0x0000000600, // 11
0x0000000000, //
0x0000000000, //
},
{//36 (2)
0x0000000000, //
0x0000000000, //
0x000000fe00, // 1111111
0x000001ff80, // 1111111111
0x000007ffc0, // 1111111111111
0x00000783c0, // 1111 1111
0x00000f03c0, // 1111 1111
0x00000e01c0, // 111 111
0x00000e01c0, // 111 111
0x00000e01c0, // 111 111
0x00000003c0, // 1111
0x00000003c0, // 1111
0x00000007c0, // 11111
0x0000001f80, // 111111
0x0000003f00, // 111111
0x000000fe00, // 1111111
0x000001fc00, // 1111111
0x000003f000, // 111111
0x000007e000, // 111111
0x000007c000, // 11111
0x00000f8000, // 11111
0x00000f8000, // 11111
0x00000fffc0, // 11111111111111
0x000007ffc0, // 1111111111111
0x0000000000, //
0x0000000000, //
},
{//37 (4)
0x0000000000, //
0x0000000000, //
0x0000000780, // 1111
0x0000000f80, // 11111
0x0000001f80, // 111111
0x0000001f80, // 111111
0x0000001f80, // 111111
0x0000003fe0, // 111111111
0x0000007fc0, // 111111111
0x000000ffe0, // 11111111111
0x000001f7c0, // 11111 11111
0x000001e600, // 1111 11
0x000003c700, // 1111 111
0x000007cf00, // 11111 1111
0x0000078f80, // 1111 11111
0x00000fffc0, // 11111111111111
0x00000fffc0, // 11111111111111
0x00000fffc0, // 11111111111111
0x00000fffc0, // 11111111111111
0x00000fff80, // 1111111111111
0x0000000f80, // 11111
0x0000000780, // 1111
0x0000000700, // 111
0x0000000700, // 111
0x0000000000, //
0x0000000000, //
},
{//38 (6)
0x0000000000, //
0x000000ff00, // 11111111
0x000001ff80, // 1111111111
0x000003ffc0, // 111111111111
0x00000381c0, // 111 111
0x0000078000, // 1111
0x0000078000, // 1111
0x000007fe00, // 1111111111
0x000007ff80, // 111111111111
0x000007ffc0, // 1111111111111
0x000007efc0, // 111111 111111
0x00000781e0, // 1111 1111
0x00000781e0, // 1111 1111
0x00000780e0, // 1111 111
0x00000780e0, // 1111 111
0x00000780e0, // 1111 111
0x00000700e0, // 111 111
0x00000781e0, // 1111 1111
0x00000381c0, // 111 111
0x000003c3c0, // 1111 1111
0x000001ff00, // 111111111
0x000000ff00, // 11111111
0x0000000000, //
0x0000000000, //
0x0000000000, //
0x0000000000, //
},
{//39 (6)
0x0000000000, //
0x0000000300, // 11
0x0000003c00, // 1111
0x0000000000, //
0x0000000000, //
0x0000000000, //
0x0000000000, //
0x0000003c00, // 1111
0x000000ff00, // 11111111
0x000001cf80, // 111 11111
0x0000038180, // 111 11
0x0000038000, // 111
0x0000038000, // 111
0x000003b800, // 111 111
0x000003ff00, // 1111111111
0x000003ff80, // 11111111111
0x000003e780, // 11111 1111
0x00000381c0, // 111 111
0x00000381c0, // 111 111
0x0000030080, // 11 1
0x0000000000, //
0x0000000000, //
0x0000000000, //
0x0000000000, //
0x0000000000, //
0x0000000000, //
},
{//40 (6)
0x0000000000, //
0x0000001c00, // 111
0x000000f000, // 1111
0x0000000000, //
0x0000000000, //
0x0000000000, //
0x0000000000, //
0x000000f800, // 11111
0x000003fc00, // 11111111
0x000007be00, // 1111 11111
0x00000f0600, // 1111 11
0x00000e0000, // 111
0x00000e0000, // 111
0x00000fe000, // 1111111
0x00000ffc00, // 1111111111
0x00000ffe00, // 11111111111
0x00000f9e00, // 11111 1111
0x00000e0700, // 111 111
0x00000e0700, // 111 111
0x00000c0700, // 11 111
0x0000000000, //
0x0000000000, //
0x0000000000, //
0x0000000000, //
0x0000000000, //
0x0000000000, //
},
};
Файл virtuino_pins.h
//---VirtuinoCM Library settings --------------
#include "VirtuinoCM.h"
VirtuinoCM virtuino;
#define V_memory_count 32 // the size of V memory. You can change it to a number <=255)
float V[V_memory_count]; // This array is synchronized with Virtuino V memory. You can change the type to int, long etc.
//---
boolean debug = false; // set this variable to false on the finale code to decrease the request time.
WiFiServer server(80); // Default Virtuino Server port
//Опеределенее ячеек памяти на сервере virtuino
#define V_lastCommTime 0 //V0 время соединения используется для первоначального сброса видимости кнопок в приложении
#define V_number_of_sum_frames 1 //V1 - число кадров суммирования
#define V_offset_y 2 //V2 - смещенее по оси Y при суммировании кадров и отображении на дисплее 75 85 87
#define V_offset_x 3 //V3 - смещенее по оси X при суммировании кадров и отображении на дисплее
#define V_level_dispalay_ttf_B_W 4 //V4 - Доп. уровень бинаризации для дисплея 15
#define V_level_find_digital_Y 5 //V5 - Доп. уровень бинаризации для поиска цифр по y
#define V_level_find_digital_X 6 //V6 - Доп. уровень бинаризации для поиска цифр по X
#define V_level_convert_to_32 7 //V7 - Доп. уровень бинаризации при конвертации в 32 бита
#define V_level_Y_up 8 //V8 - Положенее шторки сверху Y_up
#define V_level_Y_down 9 //V9 - Положенее шторки снизу Y_down
#define V_show_digital 10 //V10 - номер цифры какую выводим на экран для сравнения 8 - нет вывода
#define V_offset_x_digital 11 //V11 - смещенее по оси X при отображении на дисплеи для анализа 50 100 150
#define V_GBW 12 //V12 - 0 = gray 1 - b/w
#define V_SH_0_1 13 //V13 - вывод на монитор значения гистограммы в двоичном виде
#define V_SH_HEX 14 //V14 - вывод на монитор в HEX цифр шкалы
#define V_SH_M3 15 //V15 - вывод на монитор результатов накопленного газа
#define V_m3 16 //V16 - текущее значенее объема газа умноженное на 100
#define V_m3_minutes 17 //V17 - значенее минут при текущем объеме газа
#define V_D0 18 //V18 - опознанная цифра 1
#define V_D1 19 //V19 - опознанная цифра 2
#define V_D2 20 //V20 - опознанная цифра 3
#define V_D3 21 //V21 - опознанная цифра 4
#define V_D4 22 //V22 - опознанная цифра 5
#define V_D5 23 //V23 - опознанная цифра 6
#define V_D6 24 //V24 - опознанная цифра 7
#define V_D7 25 //V25 - опознанная цифра 8
#define V_26_error_recognition 26 //V26 - ошибки распознавания по сравнению с предыдущим
#define V_27 27 //V27 -
#define V_28 28 //V28 -
#define V_29 29 //V29 -
#define V_30 30 //V30 -
#define V_Total_run 31 //V31 -
String T_0 = ""; // результаты распознавания
String T_1 = ""; // расстояние Хемминга
String T_2 = ""; // частоты повторения цифр
//предопределенные значения
#define old_number_of_sum_frames 5 //число кадров суммирования
#define old_offset_y 83 //смещенее по оси Y при суммировании кадров и отображении на дисплее 54 86
#define old_offset_x 0 //смещенее по оси X при суммировании кадров и отображении на дисплее
#define old_level_dispalay_ttf_B_W 30 //Доп. уровень бинаризации для дисплея 30
#define old_level_find_digital_Y 30 //Доп. уровень бинаризации для поиска цифр по y 30
#define old_level_find_digital_X 70 //Доп. уровень бинаризации для поиска цифр по X 60 70
#define old_level_convert_to_32 30 //Доп. уровень бинаризации при конвертации в 32 бита 15 73 25 50
#define old_level_Y_up 22 //Положенее шторки сверху Y_up 23
#define old_level_Y_down 48 //Положенее шторки снизу X_down 48
#include <EEPROM.h>
//адреса хранения предопределеных значений в EEPROM
#define offset_y_addr 0 //смещенее по оси Y при суммировании кадров и отображении на дисплее
#define offset_x_addr offset_y_addr + sizeof(byte) //смещенее по оси X при суммировании кадров и отображении на дисплее
#define level_find_digital_Y_addr offset_x_addr + sizeof(byte) //Доп. уровень бинаризации для поиска цифр по y
#define level_find_digital_X_addr level_find_digital_Y_addr + sizeof(byte) //Доп. уровень бинаризации для поиска цифр по X
#define level_convert_to_32_addr level_find_digital_X_addr + sizeof(byte) //Доп. уровень бинаризации при конвертации в 32 бита
#define level_Y_up_addr level_convert_to_32_addr + sizeof(byte) //Положенее шторки сверху Y_up
#define level_Y_down_addr level_Y_up_addr + sizeof(byte) //Положенее шторки снизу X_down
//================================================================= check_limits
bool check_limits(float &V_test, uint8_t V_max, uint8_t V_min, uint8_t V_set, uint8_t V_MI, uint8_t V_addr)
{
//Проверка на нахождение в допустимых прелах переменной V_test
//V_test - значение, который нужно проверить
//V_max - максимальное значение передела
//V_min - минимальное значение передал
//V_set - установить если вне предлов
//V_MI - ячейка памяти в virtuino
//V_addr - адрес ячейки для сохранения
// printf("На входе: V_set %d\tV_test %f4\tV_old %d\n", V_set, V_test, EEPROM.readByte(V_addr));
if ((uint8_t)(V_test) >= V_max || (uint8_t)(V_test) < V_min || isnan(V_test))
{ //если значение вне пределов или неопределено - nan
// Serial.print("Установка первоначального значения: V_set " + (String)V_set
// + "\tV_test " + (String)V_test + "\tV_MI " + (String)V_MI + "\tV_V_addr " + (String)V_addr);
// Serial.printf("Установка первоначального значения: V_set %f\tV_test %f\tV_MI %d\tV_addr %d\n", V_set, V_test, V_MI, V_addr);
V_test = V_set; //не будет изменения восстанавливаем первоначальное
V[V_MI] = V_test;
if (EEPROM.readByte(V_addr) == (uint8_t)(V_test)) //если значение в памяти совпадает не записывать
return false;
EEPROM.writeByte(V_addr, (uint8_t)(V_test)); //сохранить в памяти EEPROM
return true;
}
else
{
if (EEPROM.readByte(V_addr) != (uint8_t)(V_test)) //если значение в памяти не совпадает записать
{
EEPROM.writeByte(V_addr, (uint8_t)(V_test));
printf("Установить и сохранить значение: V_set %d\tV_test %d\tV_MI %d\tV_addr %d\n", V_set, (uint8_t)(V_test), V_MI, V_addr);
return true;
}
else
return false;
}
}
//================================================================= check_limits
//================================================================= change_variables
void change_variables(bool read_from_memory)
//read_from_memory true восстановить из памяти
{
boolean write_EEPROM_flag = false;
//смещенее по оси Y при суммировании кадров и отображении на дисплее 20 250
if (read_from_memory) V[V_offset_y] = EEPROM.readByte(offset_y_addr); //восстановить значение из памяти
write_EEPROM_flag |= check_limits(V[V_offset_y], 250, 20, old_offset_y, V_offset_y, offset_y_addr);
//смещенее по оси X при суммировании кадров и отображении на дисплее 0 - 50
if (read_from_memory) V[V_offset_x] = EEPROM.readByte(offset_x_addr); //восстановить значение из памяти
write_EEPROM_flag |= check_limits(V[V_offset_x], 50, 0, old_offset_x, V_offset_x, offset_x_addr);
//Доп. уровень бинаризации для поиска цифр по y 30
if (read_from_memory) V[V_level_find_digital_Y] = EEPROM.readByte(level_find_digital_Y_addr); //восстановить значение из памяти
write_EEPROM_flag |= check_limits(V[V_level_find_digital_Y], 100, 0, old_level_find_digital_Y, V_level_find_digital_Y, level_find_digital_Y_addr);
//Доп. уровень бинаризации для поиска цифр по X 60 70 80
if (read_from_memory) V[V_level_find_digital_X] = EEPROM.readByte(level_find_digital_X_addr); //восстановить значение из памяти
write_EEPROM_flag |= check_limits(V[V_level_find_digital_X], 150, 0, old_level_find_digital_X, V_level_find_digital_X, level_find_digital_X_addr);
//Доп. уровень бинаризации при конвертации в 32 бита 15 73 25 50
if (read_from_memory) V[V_level_convert_to_32] = EEPROM.readByte(level_convert_to_32_addr); //восстановить значение из памяти
write_EEPROM_flag |= check_limits(V[V_level_convert_to_32], 150, 0, old_level_convert_to_32, V_level_convert_to_32, level_convert_to_32_addr);
//Положенее шторки сверху Y_up 23
if (read_from_memory) V[V_level_Y_up] = EEPROM.readByte(level_Y_up_addr); //восстановить значение из памяти
write_EEPROM_flag |= check_limits(V[V_level_Y_up], 100, 10, old_level_Y_up, V_level_Y_up, level_Y_up_addr);
//Положенее шторки снизу X_down 44
if (read_from_memory) V[V_level_Y_down] = EEPROM.readByte(level_Y_down_addr); //восстановить значение из памяти
write_EEPROM_flag |= check_limits(V[V_level_Y_down], 150, 10, old_level_Y_down, V_level_Y_down, level_Y_down_addr);
//запись данных в память
if (write_EEPROM_flag)
{ //записать данные в память
EEPROM.commit(); //подтвердить запись в память
printf("Обновлены данные в памяти\n");
}
}
//================================================================= change_variables
//================================================================= init_V
void init_V() {
boolean write_EEPROM_flag = false;
//инициализация переменных
change_variables(true);
V[V_number_of_sum_frames] = old_number_of_sum_frames; //число кадров суммирования
// V[V_level_dispalay_ttf_B_W] = old_level_dispalay_ttf_B_W; //Доп. уровень бинаризации для дисплея 15
V[V_level_dispalay_ttf_B_W] = V[V_level_convert_to_32]; //Доп. уровень бинаризации для дисплея совпдает с уровнем уровень бинаризации при конвертации в 32 бита
V[V_show_digital] = 8; //V10 - номер цифры какую выводим на экран для сравнения 8 - нет вывода
V[V_offset_x_digital] = 0; //V11 - смещенее по оси X при отображении на дисплеи для анализа 50 100 150
V[V_GBW] = 0; //V12 - 0- - b/w 1 - gray 2 - gray full
V[V_SH_0_1] = 0; //V13 - вывод на монитор значения гистограммы в двоичном виде
V[V_SH_HEX] = 0; //V14 - вывод на монитор в HEX цифр шкалы
V[V_SH_M3] = 0; //V15 - вывод на монитор результатов накопленного газа
V[V_26_error_recognition] = 0.0; //ошибки распознавания нет
}
//================================================================= init_V
//================================================================= onReceived
/* This function is called every time Virtuino app sends a request to server to change a Pin value
The 'variableType' can be a character like V, T, O V=Virtual pin T=Text Pin O=PWM Pin
The 'variableIndex' is the pin number index of Virtuino app
The 'valueAsText' is the value that has sent from the app */
void onReceived(char variableType, uint8_t variableIndex, String valueAsText) {
if (variableType == 'V') {
float value = valueAsText.toFloat(); // convert the value to float. The valueAsText have to be numerical
if (variableIndex < V_memory_count) V[variableIndex] = value; // copy the received value to arduino V memory array
}
/*
else if (variableType=='T'){
if (variableIndex==0) T_0=valueAsText; // Store the text to the text variable T0
else if (variableIndex==1) T_1=valueAsText; // Store the text to the text variable T1
else if (variableIndex==2) T_2=valueAsText; // Store the text to the text variable T2
//else if (variableIndex==3) T_3=valueAsText; // Store the text to the text variable T3
}
*/
}
//================================================================= onReceived
//================================================================= onRequested
/* This function is called every time Virtuino app requests to read a pin value*/
String onRequested(char variableType, uint8_t variableIndex) {
char S[30];
if (variableType == 'V') {
if (variableIndex < V_memory_count) {
return String(V[variableIndex]);
// sprintf(S, "%.6f", V[variableIndex]); //увеличенее до 6 знаков после запятой
// return S; // return the value of the arduino V memory array
}
}
else if (variableType == 'T') {
if (variableIndex == 0) return T_0;
else if (variableIndex == 1) return T_1;
else if (variableIndex == 2) return T_2;
//else if (variableIndex==3) return T_3;
}
return "";
}
//================================================================= onRequested
//================================================================= virtuinoRun
void virtuinoRun() {
WiFiClient client = server.available();
if (!client) return;
if (debug) Serial.println("Connected");
unsigned long timeout = millis() + 3000;
while (!client.available() && millis() < timeout) delay(1);
if (millis() > timeout) {
Serial.println("timeout");
client.flush();
client.stop();
return;
}
virtuino.readBuffer = ""; // clear Virtuino input buffer. The inputBuffer stores the incoming characters
while (client.available() > 0) {
char c = client.read(); // read the incoming data
virtuino.readBuffer += c; // add the incoming character to Virtuino input buffer
if (debug) Serial.write(c);
}
client.flush();
if (debug) Serial.println("\nReceived data: " + virtuino.readBuffer);
String* response = virtuino.getResponse(); // get the text that has to be sent to Virtuino as reply. The library will check the inptuBuffer and it will create the response text
if (debug) Serial.println("Response : " + *response);
client.print(*response);
client.flush();
delay(10);
client.stop();
// lastCommTime = millis();
V[V_lastCommTime] = round(millis() / 1000.0); //преобразовать в секунды
if (debug) Serial.println("Disconnected");
}
//================================================================= virtuinoRun
//================================================================= vDelay
void vDelay(int delayInMillis) {
unsigned long t = millis();
while ((unsigned long)(millis() - t) < delayInMillis)
virtuinoRun();
}
//================================================================= vDelay
Остання редакція NickVectra (2020-03-30 09:15:25)
Неактивний
Круто!
Молодец. Огромная работа. Ждём продолжения.
А какие потребители газа висят на этом счётчика?
Неактивний
Мне также понравилость такое решение распознавания цифр. Но думаю, что алгоритм ещё потребует доработки. Он может не сгодиться для счетчиков с другими шрифтами. А в общем, впечатляет. Класс!
Неактивний
А какие потребители газа висят на этом счётчика?
Это квартира. Из потребителей двухконтурный газовый котел 24 кВт и газовая плита.
Но думаю, что алгоритм ещё потребует доработки. Он может не сгодиться для счетчиков с другими шрифтами.
Набор эталонов создается очень легко. При нажатии на кнопку HEX на монитор выводятся наборы из текущих цифр, которые нужно просто вставить в программу. При изменении показаний и появлении новых цифр - расширяем набор эталонов и т. д. Есть отдельная программка, которая проверяет наборы и дает готовый текст программы.
У меня распознавание тестировалось для двух счетчиков тех, которые были под рукой.
Полностью согласен, что алгоритм требует доработки. Если кто-то возьмет за основу идею может совместно и доработаем
Думаю здесь вполне можно реализовать распознавание с помощью нейронной сети. Задача обсосаная и решений полно. На одном шрифте обучение должно быть простым.
Думал про распознавание с помощью нейронной сети, но не хватило тямы как это просто реализовать на данном процессоре. Решений много для обычного компьютера включая готовые библиотеки, но задействовать еще и его как-то не хотелось.
Время покажет может нужно будет вернуться к этому.
Всем спасибо
Остання редакція NickVectra (2020-03-30 10:04:48)
Неактивний
Полностью согласен, что алгоритм требует доработки. Если кто-то возьмет за основу идею может совместно и доработаем
Сейчас все блымки и врашалки считают. Можно и так, если сделать надёжный счётчик. Мне один тальянец предлагал на 8-ми разрядном микроконроллере распознавание цифр сделать; шутник. Хотя, думаю возможно, с внешней памятью. Распознавание цифр, это хороший и возможно более правильный вариант. Если оптимизировать прибор - может получится продукт. Но всё-таки это еще дороговато, для масового применения.
Неактивний
Batu пише:Думаю здесь вполне можно реализовать распознавание с помощью нейронной сети. Задача обсосаная и решений полно. На одном шрифте обучение должно быть простым.
Думал про распознавание с помощью нейронной сети, но не хватило тямы как это просто реализовать на данном процессоре. Решений много для обычного компьютера включая готовые библиотеки, но задействовать еще и его как-то не хотелось.
Время покажет может нужно будет вернуться к этому.Всем спасибо
здесь видео доступно.
https://youtu.be/IHZwWFHWa-w?t=850
Более подробно и есть еще видео
https://vas3k.ru/blog/machine_learning/
А что за процессор?
Остання редакція Batu (2020-03-30 13:56:49)
Неактивний
NickVectra пише:Полностью согласен, что алгоритм требует доработки. Если кто-то возьмет за основу идею может совместно и доработаем
Мне один тальянец предлагал на 8-ми разрядном микроконроллере распознавание цифр сделать; шутник. Хотя, думаю возможно, с внешней памятью.
"На показанных фотографиях [ширина цифр] равна 45 пикселям (Ymid), высота цифр 21 пикселям (Y_d)", и вы можете добавить порядка двух внутренних уровней по 16-20 узлов, а так же иметь один 10 разрядный уровень на выходе. Итого 45*21+2*20+10 = 995 байтов займет нейронная сеть. Плюс несколько десятков байтов на другие структуры. Само собой, веса тренированной сети прописаны в ПЗУ.
Суммирование операция простая, скорость тут тоже не требуется. Даже если цифра будет проворачиваться в момент получения фотографии и ее нельзя будет распознать, то через 5 секунд это уже станет возможным. С дополнительным контролем результата (число должно все время увеличиваться, цифры в старших разрядах не должны хаотично менять свои значения) можно избежать случайных ошибок. Кроме того, можно даже вести карту вероятностей появления каждой цифры - счетчик ведь работает предсказуемым образом. Она тоже займет порядка 10*N байтов, где N - количество разрядов на счетчике. Зато вероятность ошибки может быть получится свести чуть ли не до нуля.
Остання редакція Mishka (2020-03-30 14:10:27)
Неактивний
Вячеслав Азаров пише:NickVectra пише:Полностью согласен, что алгоритм требует доработки. Если кто-то возьмет за основу идею может совместно и доработаем
Мне один тальянец предлагал на 8-ми разрядном микроконроллере распознавание цифр сделать; шутник. Хотя, думаю возможно, с внешней памятью.
"На показанных фотографиях [ширина цифр] равна 45 пикселям (Ymid), высота цифр 21 пикселям (Y_d)", и вы можете добавить порядка двух внутренних уровней по 16-20 узлов, а так же иметь один 10 разрядный уровень на выходе. Итого 45*21+2*20+10 = 995 байтов займет нейронная сеть. Плюс несколько десятков байтов на другие структуры.
Так кто вам мешает? Покажите класс. Восьмиразрядные микроконтроллеры даже интерфейса, к этим видеосенсорам, не имеют. Нужно будет делать самостоятельно. Минимальная скорость чтения которых 5 Мбайт/с, Объем данных сами вычислите. Эти сенсоры не имеют внутреннй памяти изображения, это ПЗС и выборочно читать отдельные области картинки невозможно.
Неактивний
А что за процессор?
Процессор ESP32-CAM
"На показанных фотографиях [ширина цифр] равна 45 пикселям (Ymid), высота цифр 21 пикселям (Y_d)", и вы можете добавить порядка двух внутренних уровней по 16-20 узлов, а так же иметь один 10 разрядный уровень на выходе. Итого 45*21+2*20+10 = 995 байтов займет нейронная сеть. Плюс несколько десятков байтов на другие структуры. Само собой, веса тренированной сети прописаны в ПЗУ.
Суммирование операция простая, скорость тут тоже не требуется. Даже если цифра будет проворачиваться в момент получения фотографии и ее нельзя будет распознать, то через 5 секунд это уже станет возможным. С дополнительным контролем результата (число должно все время увеличиваться, цифры в старших разрядах не должны хаотично менять свои значения) можно избежать случайных ошибок. Кроме того, можно даже вести карту вероятностей появления каждой цифры - счетчик ведь работает предсказуемым образом. Она тоже займет порядка 10*N байтов, где N - количество разрядов на счетчике. Зато вероятность ошибки может быть получится свести чуть ли не до нуля.
Ymid - это середина цифры по оси Y
Высота (height_letter) и ширина цифры (width_letter) принята по 26 пикселей.
Ширина цифры после определения занимает от 16 до 3 пикселей в зависимости от того какая цифра.
В высоте также участвует возможный сдвиг цифры, когда она занимает промежуточное положение.
У меня так просто, к сожалению, не получилось разобраться в нейронной сети даже самой простейшей.
Неактивний
Так кто вам мешает? Покажите класс. Восьмиразрядные микроконтроллеры даже интерфейса, к этим видеосенсорам, не имеют. Нужно будет делать самостоятельно. Минимальная скорость чтения которых 5 Мбайт/с, Объем данных сами вычислите. Эти сенсоры не имеют внутреннй памяти изображения, это ПЗС и выборочно читать отдельные области картинки невозможно.
Мне ничего не мешает. Но я ведь и не должен?
В ESP32-CAM, похоже, стоит камера OV2640. У нее есть возможность отображать только интересующий регион (region of interest - ROI). Интерфейс с кадровым буфером делать тоже не нужно.
В принципе вполне возможно читать и распознавать цифра за цифрой все значения счетчика. Похоже, что на Uno с ее Mega2560 это сделать достаточно комфортно. Но теоретически можно попробовать уложиться и в 2Кб ОЗУ, переместив предварительно обученную сеть в ПЗУ.
Конечно же, я совсем не имею ввиду, что нужно заменить ESP32-CAM на 8-битный контроллер. Но и утверждать, что Ваш знакомый просто неудачно пошутил, тоже не могу.
Неактивний
Ymid - это середина цифры по оси Y
Высота (height_letter) и ширина цифры (width_letter) принята по 26 пикселей.
Ширина цифры после определения занимает от 16 до 3 пикселей в зависимости от того какая цифра.
В высоте также участвует возможный сдвиг цифры, когда она занимает промежуточное положение.
У меня так просто, к сожалению, не получилось разобраться в нейронной сети даже самой простейшей.
Ага, вижу, извините за невнимательность. Размер получается еще меньше!
Возможно, Вам покажется интересным классический курс по машинному обучению авторства Эндрю Ына. Нейронные сети там объяснены очень хорошо. Вообще говоря, на практике сегодня нечасто встретишь самописную реализацию библиотеки машинного обучения - все больше готовые решения, даже для микроконтроллеров. Но они всего лишь библиотека алгоритмов, а первоочередную роль все-таки играют данные.
Неактивний
Вячеслав Азаров пише:Так кто вам мешает? Покажите класс. Восьмиразрядные микроконтроллеры даже интерфейса, к этим видеосенсорам, не имеют. Нужно будет делать самостоятельно. Минимальная скорость чтения которых 5 Мбайт/с, Объем данных сами вычислите. Эти сенсоры не имеют внутреннй памяти изображения, это ПЗС и выборочно читать отдельные области картинки невозможно.
Мне ничего не мешает. Но я ведь и не должен?
В ESP32-CAM, похоже, стоит камера OV2640. У нее есть возможность отображать только интересующий регион (region of interest - ROI). Интерфейс с кадровым буфером делать тоже не нужно.
В принципе вполне возможно читать и распознавать цифра за цифрой все значения счетчика. Похоже, что на Uno с ее Mega2560 это сделать достаточно комфортно. Но теоретически можно попробовать уложиться и в 2Кб ОЗУ, переместив предварительно обученную сеть в ПЗУ.
Конечно же, я совсем не имею ввиду, что нужно заменить ESP32-CAM на 8-битный контроллер. Но и утверждать, что Ваш знакомый просто неудачно пошутил, тоже не могу.
Не должен, конечно. Советовать и критиковать всегда проще чем самому сделать. Да вы поробуйте ещё подключите её к АТMega2560. Про возможность читать интересующий регион, с этих сенсоров, я не знал. Это упрощает дело, но не решает проблему чтения и синхронизации сенсора. В ESP32-CAM 4 Мв SPI PSRAM + столько же внутренней. Пошутил он, видимо, зная мой интерес к 8-ми разрядкам. А Ардукам имеет и видеобуфер и видеопроцессор. Только Ардуино УНО ему ещё не хватает. Речь шла о 8-ми разрядном микроконтроллере а не о GPGA Lattis!
Неактивний
Да ESP32-CAM, как специально, для этого сделана. И все на одном чипе, не считая памяти. Из чего можно такое собрать, за $5?
https://aliexpress.ru/item/32963016501.html
Да ESP32-CAM, как специально, для этого сделана. И все на одном чипе, не считая памяти. Из чего можно такое собрать, за $5?
https://aliexpress.ru/item/32963016501.html
На этой плате уже есть внешняя PSRAM 4 MB, которая используется, в том числе, для организации 2-х кадровых буферов.
Еще можно отказаться от дисплея и установленного на постоянно преобразователя USB-TTL.
А я вот думаю, не легче ли было сделать счетчики самому.. И нафига это распознавание
Это газ и кто разрешит его установить?
Насколько я читал, все даже современные счетчики (кроме электрических и тепловых) считают импульсы.
И нафига это распознавание
А просто интересная задача - что делать когда карантин
Остання редакція NickVectra (2020-03-31 09:20:36)
Неактивний
Это газ и кто разрешит его установить?
А кто разрешит навешивать на счетчик эту приблуду?
NickVectra пише:Это газ и кто разрешит его установить?
А кто разрешит навешивать на счетчик эту приблуду?
В теории она (приблуда) как бы рядом и даже не касается счетчика
Главное в его работу не вмешивается.
Естественно проверяющим лучше не показывать и перед их приходом снимать.
Вот есть электронный за 5,6 тыс. грн. с радио-модулем для передачи данных https://www.samgas.com.ua/sites/default/files/rse_instr_user_ru.pdf
но тогда будут вопрос по расшифровке отсылаемых данных.
Неактивний
Сегодня вечером добавил модуль HC-05 Bluetooth (был именно такой). Использую только для передачи данных от устройства на телефон или компьютер вместо того, что бы тянуть кабель.
С левой стороны "коробочки" есть два разъема: питание и вход/выход для программирования через USB-TTL преобразователь - он же Serial. Модуль HC-05 включил параллельно USB-TTL преобразователю на скорости 115200.
Теперь все, что передается обычно на Serial также теперь поступает по каналу Bluetooth.
В программе ничего менять не нужно.
Еще хочу сделать меленький резервный источник на одном 18650.
Сейчас на прогоне стоят два варианта: на модуле J5019 и на модуле TP4056+MT3608.
При нагрузке 0,5 А ни один из модулей не показывает полного заряда аккумулятора, хотя на нем 4,2 В.
От источника идет потребление по 0,7 А. Ну и как известно эти модули греются при зарядке.
При работе устройство потребляет 0,2-0,4 А. Поэтому сопротивление R3 модуля TP4056 заменил на 1,5 кОм уменьшив ток зарядки и модуль будет соответственно меньше греться.
Почему-то несмотря на то, что "коробка" закреплена и не движется по отношению к счетчику иногда происходит смещение изображения по оси Y. Причем это смещение может быть как вверх так и вниз. Объяснить это я не могу. Поэтому добавил еще автоматическую подстройку по оси Y. Каждые 10 измерений происходит смещение по оси Y на +/- 1 или 0 пикселей. Выбирается то значение смещения, которое дает минимальное значение суммы расстояний Хемминга для 7 цифр шкалы (без самой последней). На следующем шаге процесс повторяется.
Добавил возможность программного сброса с помощью команды ESP.restart() из приложения.
Сброс дисплея пришлось также сделать программным подключив контакт RST к GPIO13 заменив в скетче строчку
#define TFT_RST 13 // -1
Остання редакція NickVectra (2020-04-08 21:27:04)
Неактивний
Пытаюсь повторить проект с выдачей показаний в GoogleSheets табличку. С выдачей проблем нет, а для распознавания показаний Ваш проект идеально подходит.
Я так понял в актуальной версии отказались от Web интерфейса (нет файла myserver.h) в пользу Virtuino.
Не поделитесь проектом для Virtuino и пару слов как его развернуть на телефоне у себя? Не то с ходу там многовато опций непонятных.
Не пользуетесь git-hub?
Неактивний
Не поделитесь проектом для Virtuino и пару слов как его развернуть на телефоне у себя?
Скинул последние версии файлов на goodle диск
https://drive.google.com/drive/folders/ … sp=sharing
Из Play Маркета устанавливаете Virtuino (именное Virtuino).
Файл gas1-0.vrt копируете в папку virtuino/projects. Загружаете и запускаете Virtuino.
Устанавливаете правильный адреса сервера - тот, который выводится на экран монитора при запуске скетча (у меня это):
"Camera Stream Ready! Go to: http://172.20.0.35"
Выбирайте настройка сервера
Далее gas counter
Смените адрес сервера на Ваш.
При необходимости работать не только внутри локальной домашней сети, но и извне можно добавить второй сервер, например, no-ip.
Для этого нужно, чтобы соответствующий адрес и порт были видны извне - т.е. открыть этот порт в роутере.
Второй сервер NodeMCU termo у Вас думаю должен быть просто отключен - это из моего предыдущего проекта по управлению котлом я беру данные для построения графика режима работы котла (страница 1 в приложении Virtuino).
У меня версия Virtuino платная поэтому нет ограничений на количество виджетов. Если загрузите мой gas1-0.vrt, то работать должен, но редактировать, наверное не даст.
В бесплатной версии можно добавлять, кажется, до 10 виджетов, думаю этого достаточно для тестирования.
Значение виджета - это элемент массива V[] в скетче описание которого в файле virtuino_pins.h.
Если в приложении Virtuino Вы строите график, например м3, то потом накопленные значения можно сохранить в формате Excel.
Кроме того, как я писал выше после добавления модуля с Bluetooth накопленные данные можно легко вывести на экран монитора и также сохранить в в формате Excel.даже без физического подключения.
Добавил сбор статистики по использованию эталонов. Стало интересно все ли используются.
Данные выводится на экран монитора при нажатии клавиши "м3 на монитор".
В скетче нашел пару ошибок поэтому нужно работать с последней версией.
В приложении Virtuino действительно много данных, которые наверное после окончательной отладки не нужны и можно будет значительно упростить интерфейс.
Смещение изображения, о котором я писал в предыдущем сообщении происходит если используется два буфера для камеры config.fb_count = 2. т.к. скорости достаточно, то использую config.fb_count = 1.
Готов ответить на Ваши вопросы.
Буду признателен Вам за совместную доработку.
Остання редакція NickVectra (2020-04-20 10:26:55)
Неактивний
Большое спасибо за оперативный ответ, выходной пройдет не зря )
Жаль дисплея под рукой нет, заказал только.
У меня задача - распознать показания двух водомеров в поле зрения ESP-CAM и выдать показания в табличку GoogleSheets, экран в общем не нужен, но на этапе отладки видимо без него никак.
Неактивний
А я вот думаю, не легче ли было сделать счетчики самому.. И нафига это распознавание
Не легче. Счётчики это метрология, а это уже не кухонное дело. Точность, надёжность, стабильность, эталоны, сертификаты и т.п. А с видеокамеры, взятки гладки. Да и видеосенсор стоит сейчас $1-2. Пришёл час исскуственного ока.