uncategorized

Распознаем положение ручек на газовой плите с opencv

Всем привет, и сегодня о попытках сделать добро себе и людям. Сегодня будем искать варианты, как узнать включен газ или нет, чтобы как минимум не забыть что-то на плите. Автоматика это конечно хорошо, но 100 процентов полагаться на нее не стоит, но хорошим дополнением к повседневному быту она может быть.

История:

Первая реализация моего проекта была по статье на хабре

Съёмка показаний счетчика на телефон с последующим распознаванием https://habr.com/ru/post/220869/

Очень много полезной информации в статье, раньше у меня стоял счетчик похожего образца, и поменяв слегка код, и подкрутив значения - проект завелся.

Фото старого счетчика, не фотошоп, все семерки настоящие

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

Дано

Ручки плиты

Ручки плиты сфотографированные в упор. Черные метки на ручках сделаны специально, в идеале их стоит делать матовым лаком, чтобы не словить блик. С камеры под потолком изображение будет другое.

Raspberry Pi zero camera

Камера на RP_zero c запущенным веб-сервисом который по запросу отдает фото в большом разрешении. В начале была прошивка motion ios, но камера спустя время переставала делать фото, и не виделась системой, возможно из-за перегрева.

Интересно, что сервис этот тоже иногда отваливается. Все проверяется временем, может работать сутки, и потом перестать отвечать, пока есть мысль, что из-за потери wifi, теряет айпишник, и затем веб-сервер заново не может стартануть. Пока решил это перезапуском сервиса в crontab.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import io
import time
import picamera
# from http.server import HTTPServer, BaseHTTPRequestHandler
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer

class SimpleHTTPRequestHandler(BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(200)
self.send_header("Content-type", "image/jpeg")
self.end_headers()

print(self.path)

if self.path == "/":

my_stream = io.BytesIO()
with picamera.PiCamera() as camera:
camera.vflip = True;
camera.hflip = True;
camera.resolution = (2592, 1944)
camera.start_preview()
time.sleep(2)
camera.capture(my_stream, 'jpeg')

self.wfile.write(my_stream.getvalue())
else:
self.wfile.write(b"Hello world")



httpd = HTTPServer(("192.168.100.88", 8888), SimpleHTTPRequestHandler)
httpd.serve_forever()

Способы распознование

1 - По шаблону

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

Так же к этому способу добавлял функцию выявления контуров на изображении, но из-за смены освещения в течении дня, контуры получались очень разными, что не давало хорошего результата.

2 - По пикселям

Второй способ более простой, это привязка к пикселям, статья, которая решает задачу подобным путем опять нашлась на хабре

https://habr.com/ru/post/143102/
Распознаём изображение с токена при помощи камеры

Взяв за основу этот принцип получился рабочий вариант. Теперь камеру нужно жестко закреплять, чтобы со временем полученное изображение не ушло в сторону. Еще минус, что если подвинем плиту во время уборки, тоже программу нужно подстраивать. Или же можно сделать алгоритм поиска ручек адаптивным, привязавшись к контурам плиты.

Фото имеет красноватый оттенок, так как это камера без ИК фильтра позволяющая снимать ночью.

Работает следующем образом, на фото видны прямоугольники, один большой и 5 мелких. Программа всегда смотрит только эти прямоугольные области в не зависимости, от положения ручек плиты. С большого прямоугольника берется общая яркость для значения бекграунда плиты, с маленького берется контрольный участок каждой ручки. Так как на ручках есть черный метки, когда газ закрыт, и ручка в своем исходном положении, то яркость этих областей должна быть меньше яркость бекраунда плиты. Если ручку сдвинуть, то яркость прямоугольника ответственного за ручку возрастет, и это будет значить, что газ открыт. Так же где ручки открыты, рисуем более жирную рамку.

Пример данных после обработки фото:

Background = 25866
Ручка 1 = 23738
Ручка 2 = 28174 <– газ открыт
Ручка 3 = 22161
Ручка 4 = 24229
Ручка 5 = 23925

Этот алгоритм отработал в полной темноте, при ИК подсветки самой камеры.

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

Тестовый код который был использован на то время.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135

import cv2
import numpy as np
import requests
from math import sqrt, floor
import sched, time
import copy

# pip3 install requests

s = sched.scheduler(time.time, time.sleep)

def rotate_image(image, angle):
image_center = tuple(np.array(image.shape[1::-1]) / 2)
rot_mat = cv2.getRotationMatrix2D(image_center, angle, 1.0)
result = cv2.warpAffine(image, rot_mat, image.shape[1::-1], flags=cv2.INTER_LINEAR)
return result

def count_pixels(img, x, y, w, h):
result = 0
for yy in range(y, y + h):
for xx in range(x, x + w):
result += img[yy, xx]
return result

def do_something(sc):
print("Doing ...")

url = r"http://192.168.100.88:8888/"
resp = requests.get(url, stream=True).raw
img_rgb = np.asarray(bytearray(resp.read()), dtype="uint8")
img_rgb = cv2.imdecode(img_rgb, cv2.IMREAD_COLOR)

# img_rgb = cv2.imread('./check_images/check_1593780018.5604236__506__original.jpg')

# img_rgb = cv2.imread('./night_test_0.jpg')

original_image = copy.copy(img_rgb)

img_rgb = img_rgb[1400:1700, 800:1400]
img_rgb = rotate_image(img_rgb, 24)
img_rgb = img_rgb[150:250,20:440]

gray = cv2.cvtColor(img_rgb,cv2.COLOR_BGR2GRAY)

sq0 = 10
sq1 = 15

back_x = sq0 * 34
back_y = sq1 * 3

background = (count_pixels(gray, 60, 30, back_x, back_y) / 34 / 3) - 650

print("background_before")
print(background)

if background < 10000:
brightness = 200
contrast = 100
gray = np.int16(gray)
gray = gray * (contrast/127+1) - contrast + brightness
gray = np.clip(gray, 0, 255)
gray = np.uint8(gray)
background = (count_pixels(gray, 60, 30, back_x, back_y) / 34 / 3) - 800

print("background_after")
print(background)



cv2.rectangle(gray, (60, 30), (back_x, back_y), (0, 0, 255), 1)

cfix = -8
c1_p = cfix + 61
c2_p = cfix + 154
c3_p = cfix + 212
c4_p = cfix + 275
c5_p = cfix + 340

c1 = count_pixels(gray, c1_p, 38, sq0, sq1)
c2 = count_pixels(gray, c2_p, 42, sq0, sq1)
c3 = count_pixels(gray, c3_p, 45, sq0, sq1)
c4 = count_pixels(gray, c4_p, 45, sq0, sq1)
c5 = count_pixels(gray, c5_p, 45, sq0, sq1)

count = 0
if c1 < background:
count = count + 1
if c2 < background:
count = count + 1
if c3 < background:
count = count + 1
if c4 < background:
count = count + 1
if c5 < background:
count = count + 1

cv2.rectangle(gray, (c1_p, 38), (c1_p + sq0, 38 + sq1), (0, 0, 255), c1 < background and 1 or 2)
cv2.rectangle(gray, (c2_p, 42), (c2_p + sq0, 42 + sq1), (0, 0, 255), c2 < background and 1 or 2)
cv2.rectangle(gray, (c3_p, 45), (c3_p + sq0, 45 + sq1), (0, 0, 255), c3 < background and 1 or 2)
cv2.rectangle(gray, (c4_p, 45), (c4_p + sq0, 45 + sq1), (0, 0, 255), c4 < background and 1 or 2)
cv2.rectangle(gray, (c5_p, 45), (c5_p + sq0, 45 + sq1), (0, 0, 255), c5 < background and 1 or 2)

print("background")
print(background)
print("c1")
print(c1)
print("c2")
print(c2)
print("c3")
print(c3)
print("c4")
print(c4)
print("c5")
print(c5)

print(count)

if count != 5:
ts = time.time()
# cv2.imwrite('./photos/check_' + str(ts) + '__' + str(color_ligth) + '.jpg', img_rgb)
cv2.imwrite('./photos/check_' + str(ts) + '__' + str(123) + '__gray.jpg', gray)
cv2.imwrite('./photos/check_' + str(ts) + '__' + str(123) + '__original.jpg', original_image)


# cv2.imshow("gray", gray)
# cv2.imshow("output", img_rgb)

s.enter(60, 1, do_something, (sc,))

s.enter(2, 1, do_something, (s,))
s.run()

# do_something(777)
# cv2.waitKey(0)

Испытания в один день и одну ночь, показали слабые места

TODO:

  1. Жесткое крепление камеры и адаптивный режим поиска

Из-за не жесткого крепления камера снимок съезжает каждый день по чуть-чуть. Когда это остановится не ясно) Возможно из-за того, что в старом креплении использовалась алюминиевая проволока, которая пытается раскрутиться в первоначальный свой вид. Более мощное крепление должно решить это, плюс если это все заработает адаптивный поиск ручек будет большим плюсом, так как плиту тоже можно слегка сдвинуть, а перенастраивать программу не совсем быстро, скажу честно)

  1. Улучшить качество фото или менять алгоритм

Не все положения ручки считываются правильно

Все было круто, пока одну ручку не выкрутили на полную. Стрелками обозначена эта ручка. Ручки которые программа посчитала открытыми, программа обвела жирным контуром, где закрыты там тонкий контур. Одна открытая на полную ручка не защитана, сумма яркости пикселей прямоугольника, не на много отличается значению яркости бекраунда. Как вариант можно еще уменьшить область прямоугольника.

Вывод - либо менять алгоритм, совсем не привязываться к пикселям, а использовать, к примеру алгоритм поиска ближних соседей, либо нужно улучшать качество фото освещением.

  1. Автоматическое тестирование

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

Share