Статья Создание сигнализационной системы с помощью Python.

Приветствую, Codeby.
В этой статье мы создадим свою сигнализационную систему для дома, с помощью лишь ноутбука с веб-камерой и Python

mp-10-21-21.jpg


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

Основное:
Для определения движения используются следующие библиотеки:
cv2 - получение, вывод, работа с изображениями
numpy - для более удобного проведения некоторых операций
imutils - grab_contours
time - время
collections - deque
Код уже существует и состоит из функций get_movement, get_background, detect, main
Функция get_movement будет передавать нам фреймы с изменениями:
Python:
def get_movement(frames, shape):
    movement_frame = np.zeros(shape, dtype='float32')
    i = 0
    for f in frames:
        i += 1
        movement_frame += f * i
    movement_frame = movement_frame / ((1 + i) / 2 * i)
    movement_frame[movement_frame > 254] = 255
    return movement_frame
Функция get_background позволяет получить статичный фон
Python:
def get_background(frames, shape):
    bg = np.zeros(shape, dtype='float32')
    for frame in frames:
        bg += frame
    bg /= len(frames)
    bg[bg > 254] = 255
    return bg
Функция detect будет определять сам факт движения в зависимости от заданных параметров, таких как насколько сильно изменение и какой минимальный размер объекта
Python:
def detect(frame, bg_frames, fg_frames, threshold=25, min_box=50):
    fg_frames.append(frame)
    bg_frames.append(frame)
    fg_frame = get_movement(list(fg_frames), frame.shape)
    bg_frame = get_background(list(bg_frames), frame.shape)

    movement = cv2.absdiff(fg_frame, bg_frame)
    movement[movement < threshold] = 0
    movement[movement > 0] = 254
    movement = movement.astype('uint8')
    movement = cv2.cvtColor(movement, cv2.COLOR_BGR2GRAY)
    movement[movement > 0] = 254
    cv2.imshow('Movement', movement)
    contours = cv2.findContours(movement, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    contours = imutils.grab_contours(contours)
    boxes = []
    for contour in contours:
        if cv2.contourArea(contour) < min_box:
            continue
        box = cv2.boundingRect(contour)
        boxes.append(box)
    return boxes
Ну и главная функция main которая позволяет отслеживать движения:
Python:
def main(width=640, height=480, scale_factor=3):
    bg_frames = deque(maxlen=30)
    fg_frames = deque(maxlen=10)
    cap = cv2.VideoCapture(0)
    cap.set(cv2.CAP_PROP_FRAME_WIDTH, width)
    cap.set(cv2.CAP_PROP_FRAME_HEIGHT, height)
    last_time = time.time()
    while True:
        _, frame = cap.read()
        frame = cv2.resize(frame, (width, height))
        work_frame = cv2.resize(frame, (width // scale_factor, height // scale_factor))
        work_frame = cv2.GaussianBlur(work_frame, (5, 5), 0)
        work_frame_f32 = work_frame.astype('float32')
        boxes = detect(work_frame_f32, bg_frames, fg_frames)
        if boxes != []:

        text = "FPS:" + str(int(1 / (time.time() - last_time)))
        last_time = time.time()
        cv2.putText(frame, text, (10, 20), cv2.FONT_HERSHEY_PLAIN, 2, (0, 255, 0), 2)
        cv2.imshow('Webcam', frame)
        if cv2.waitKey(1) & 0xFF == ord('q'):
            break
    cap.release()
    cv2.destroyAllWindows()
if __name__ == "__main__":
    main()

Данная программа ограничена обрисовкой движущихся объектов и при появлении движения выводит предупреждение.
Так все выглядит:

1654847891946.png


Теперь мы можем вместо отрисовки и вывода текста отправлять сообщения от нашего бота Telegram, сделаем это с помощью pyTelegramBotAPI.

Чтобы не отправлять по каждому движению 100 сообщений, мы будем отправлять сообщение при первом движении в минуте, сохранять время отправки, сохранять время 2 движения в этой минуте если оно произошло не менее чем через 20 секунд после предыдущего.

То есть:
15:00 - Движение, отправка сообщения
15:00:10 - Движение, сообщение не отправляется и не сохраняется, так как не прошло 60 или 20 секунд с момента прошлого сообщения.
15:00:20 - Движение, время сохраняется для отправки в ближайшую минуту
15:01 - Нет движений, отправка данных о движении в 15:00:20
Сделано это для того, чтобы оптимизировать нашего бота, так мы вряд-ли что-то упустим, но и не будем получать сотни сообщений в секунду
Python:
last_alert = 0
alert_in_queue = False
def motion_alert():
    global last_alert
    global alert_in_queue
    if time.time() - last_alert > 60:
        if alert_in_queue != None:
            alert_time = alert_in_queue
            bot.send_message(1823377615, "Сигнализация сработала в " + alert_time)
            alert_in_queue = None
        else:
            alert_time = datetime.now().strftime("%H:%M:%S")
            bot.send_message(1823377615, "Сигнализация сработала в " + alert_time)
            last_alert = time.time()
    elif alert_in_queue == None and time.time() - last_alert >20:
        alert_in_queue = datetime.now().strftime("%H:%M:%S")
Теперь в функции main уберем отрисовку объектов и будем вызывать motion_alert в отдельном потоке:
Python:
boxes = detect(work_frame_f32, bg_frames, fg_frames)
if boxes != [] or alert_in_queue != None:
            threading.Thread(target=motion_alert()).start()

Теперь, если в период с 15:01 по 15:02 было совершено движение и оно было первым в этой минуте, то будет прислан HTTP запрос.
Если движение уже не первое и запрос уже посылался, то alert_in_queue ставится на True и с наступлением следующей минуты система снова отправит нам запрос.
В результате на нашего бота приходят сообщения при движениях!

1654847694797.png


На этом мы не остановимся и сделаем функцию для отключения сигнализации:
Python:
@bot.message_handler(commands=['start_alert'])
def start_alert(message):
    bot.reply_to(message, "Сигнализация активирована. До скорого возращения!")
    signalisation = threading.Thread(target=main())
    signalisation.start()
@bot.message_handler(commands=['stop_alert'])
def start_alert(message):
    global stop_alert
    stop_alert = True
    bot.reply_to(message, "Сигнализация деактивирована. Добро пожаловать домой!")
bot.infinity_polling()
В функцию main добавим отключение при становлении stop_alert True:
Python:
if stop_alert == True:
    break

Итоговый результат:

1654847665700.png


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

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

Polyglot

Green Team
25.02.2020
27
40
Насчет "турели" улыбнуло - к пулеметной что-ли :) ?
Но даже для технической статьи стоит прогнать через правку ошибок,
хотя бы для выработки привычки - начнешь со временем поиск работы
а резюме с ошибками сразу в топку идут, без вариантов.

предлагают компании, госурадство,
если датчк засекает
сильно изминение
нежелающим ставить > не желающим
 
Последнее редактирование:

T1000

New member
02.09.2020
1
0
жду вторую часть о пожарной сигнализации
 

Евгений Л

New member
21.04.2019
4
0
Отличная статья,надо добавить дозвон по номеру и голосовое оповещение )
 
Мы в соцсетях: