Управляем звуком ПК от активности пользователя с помощью Python

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!

Настройка программного обеспечения

Без промедления начнём. Нам нужно установить следующее ПО:

  • Windows 10

  • Anaconda 3 (Python 3.8)

  • Visual Studio 2019 (Community) - объясню позже, зачем она понадобится.

Открываем Anaconda Prompt (Anaconda3) и устанавливаем следующие пакеты:

pip install opencv-python
pip install dlib
pip install face_recognition

И уже на этом моменте начнутся проблемы с dlib.

Решаем проблему с dlib

Я перепробовал все решения, что нашёл в интернете и они оказались неактуальными - раз, два, три, официальное руководство и видео есть. Поэтому будем собирать пакет вручную.

Итак, первая же ошибка говорит о том, что у нас не установлен cmake.

ERROR: CMake must be installed to build dlib
ERROR: CMake must be installed to build dlib
ERROR: CMake must be installed to build dlib

Не закрывая консоль, вводим следующую команду:

pip install cmake
Проблем при установке быть не должно

Пробуем установить пакет той же командой (pip install dlib), но на этот раз получаем новую ошибку:

Отсутствуют элементы Visual Studio

Ошибка явно указывает, что у меня, скорее всего, стоит студия с элементами только для C# - и она оказывается права. Открываем Visual Studio Installer, выбираем "Изменить", в вкладке "Рабочие нагрузки" в разделе "Классические и мобильные приложения" выбираем пункт "Разработка классических приложений на С++":

Пошагово
"Изменить"
"Изменить"
Разработка классических приложений на С++
Разработка классических приложений на С++
Ждем окончания установки
Ждем окончания установки

Почему важно оставить все галочки, которые предлагает Visual Studio. У меня с интернетом плоховато, поэтому я решил не скачивать пакет SDK для Windows, на что получил следующую ошибку:

Не нашли компилятор

Я начал искать решение этой ошибки, пробовать менять тип компилятора (cmake -G " Visual Studio 16 2019"), но только стоило установить SDK, как все проблемы ушли.

Я пробовал данный метод на двух ПК и отмечу ещё пару подводных камней. Самое главное - Visual Studio должна быть 2019 года. У меня под рукой был офлайн установщик только 2017 - я мигом его поставил, делаю команду на установку пакета и получаю ошибку, что нужна свежая Microsoft Visual C++ версии 14.0. Вторая проблема была связана с тем, что даже установленная студия не могла скомпилировать проект. Помогла дополнительная установка Visual C++ 2015 Build Tools и Microsoft Build Tools 2015.

Открываем вновь Anaconda Prompt, используем ту же самую команду и ждём, когда соберется проект (около 5 минут):

Сборка
Всё прошло успешно
Всё прошло успешно

Управляем громкостью

Вариантов оказалось несколько (ссылка), но чем проще - тем лучше. На русском язычном StackOverflow предложили использовать простую библиотеку от Paradoxis - ей и воспользуемся. Чтобы установить её, нам нужно скачать архив, пройти по пути C:\ProgramData\Anaconda3\Lib и перенести файлы keyboard.py, sound.py из архива. Проблем с использованием не возникало, поэтому идём дальше

Собираем события мыши

Самым популярным модулем для автоматизации управления мышью/клавиатурой оказался pynput. Устанавливаем так же через (pip install dlib). У модуля в целом неплохое описание - https://pynput.readthedocs.io/en/latest/mouse.html . Но у меня возникли сложности при получении событий. Я написал простую функцию:

from pynput import mouse

def func_mouse():
        with mouse.Events() as events:
            for event in events:
                if event == mouse.Events.Scroll or mouse.Events.Click:
                    #print('Переместил мышку/нажал кнопку/скролл колесиком: {}\n'.format(event))
                    print('Делаю половину громкости: ', time.ctime())
                    Sound.volume_set(volum_half)
                    break

Самое интересное, что если раскомментировать самую первую строчку и посмотреть на событие, которое привело выходу из цикла, то там можно увидеть Move. Если вы заметили, в условии if про него не слово. Без разницы, делал я только скролл колесиком или только нажатие любой клавиши мыши - все равно просто движение мыши приводит к выходу из цикла. В целом, мне нужно все действия (Scroll, Click, Move), но такое поведение я объяснить не могу. Возможно я где-то ошибаюсь, поэтому можете поправить.

А что в итоге?

Adam Geitgey, автор библиотеки face recognition, в своём репозитории имеет очень хороший набор примеров, которые многие используют при написании статей: https://github.com/ageitgey/face_recognition/tree/master/examples

Воспользуемся одним из них и получим следующий код, который можно скачать по ссылке: Activity.ipynb, Activity.py

Код
# Подключаем нужные библиотеки
import cv2
import face_recognition
 
# Получаем данные с устройства (веб камера у меня всего одна, поэтому в аргументах 0)
video_capture = cv2.VideoCapture(0)
 
# Инициализируем переменные
face_locations = []

from sound import Sound
Sound.volume_up() # увеличим громкость на 2 единицы
current = Sound.current_volume() # текущая громкость, если кому-то нужно

volum_half=50  # 50% громкость
volum_full=100 # 100% громкость

Sound.volume_max() # выставляем сразу по максимуму


# Работа со временем

# Подключаем модуль для работы со временем
import time
# Подключаем потоки
from threading import Thread
import threading


# Функция для работы с активностью мыши
from pynput import mouse

def func_mouse():
        with mouse.Events() as events:
            for event in events:
                if event == mouse.Events.Scroll or mouse.Events.Click:
                    #print('Переместил мышку/нажал кнопку/скролл колесиком: {}\n'.format(event))
                    print('Делаю половину громкости: ', time.ctime())
                    Sound.volume_set(volum_half)
                    break

# Делаем отдельную функцию с напоминанием
def not_find():
    #print("Cкрипт на 15 секунд начинается ", time.ctime())
    print('Делаю 100% громкости: ', time.ctime())
    #Sound.volume_set(volum_full)
    Sound.volume_max()
    
    # Секунды на выполнение
    #local_time = 15
    # Ждём нужное количество секунд, цикл в это время ничего не делает
    #time.sleep(local_time)
    
    # Вызываю функцию поиска действий по мышке
    func_mouse()
    #print("Cкрипт на 15 сек прошел")


# А тут уже основная часть кода

while True:
    ret, frame = video_capture.read()
    
    '''
    # Resize frame of video to 1/2 size for faster face recognition processing
    small_frame = cv2.resize(frame, (0, 0), fx=0.50, fy=0.50)
    rgb_frame = small_frame[:, :, ::-1]
    '''

    rgb_frame = frame[:, :, ::-1]
    
    face_locations = face_recognition.face_locations(rgb_frame)
    
    number_of_face = len(face_locations)
    
    '''
    #print("Я нашел {} лицо(лица) в данном окне".format(number_of_face))
    #print("Я нашел {} лицо(лица) в данном окне".format(len(face_locations)))
    '''
    
    if number_of_face < 1:
        print("Я не нашел лицо/лица в данном окне, начинаю работу:", time.ctime())
        '''
        th = Thread(target=not_find, args=()) # Создаём новый поток
        th.start() # И запускаем его
        # Пока работает поток, выведем на экран через 10 секунд, что основной цикл в работе
        '''
        #time.sleep(5)
        print("Поток мыши заработал в основном цикле: ", time.ctime())
        
        #thread = threading.Timer(60, not_find)
        #thread.start()
        
        not_find()
        '''
        thread = threading.Timer(60, func_mouse)
        thread.start()
        print("Поток мыши заработал.\n")
        # Пока работает поток, выведем на экран через 10 секунд, что основной цикл в работе
        '''
        #time.sleep(10)
        print("Пока поток работает, основной цикл поиска лица в работе.\n")
    else:
        #все хорошо, за ПК кто-то есть
        print("Я нашел лицо/лица в данном окне в", time.ctime())
        Sound.volume_set(volum_half)
        
    for top, right, bottom, left in face_locations:
        cv2.rectangle(frame, (left, top), (right, bottom), (0, 0, 255), 2)
    
    cv2.imshow('Video', frame)
    
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break


video_capture.release()
cv2.destroyAllWindows()

Суть кода предельно проста: бегаем в цикле, как только появилось хотя бы одно лицо (а точнее координаты), то звук делаем 50%. Если не нашёл никого поблизости, то запускаем цикл с мышкой.

Тестирование в бою

Ожидание и реальность

Если вы посмотрели видео, то поняли, что результат ещё далёк от реальной эксплуатации.

Признаю честно - до этого момента никогда не сталкивался с многопоточностью на Python, поэтому "с наскоку" тему взять не удалось и результат по видео понятен. Есть неплохая статья на Хабре, описывающая различные методы многопоточности, применяемые в языке. Пока у меня решения нету по этой теме нету - будет повод разобраться лучше и дописать код/статью с учетом этого.

Так же возникает закономерный вопрос - а если вместо живого человека поставить перед монитором картинку? Да, она распознает, что, скорее всего, не совсем верно. Мне попался очень хороший материал по поводу определения живого лица в реальном времени - https://www.machinelearningmastery.ru/real-time-face-liveness-detection-with-python-keras-and-opencv-c35dc70dafd3/ , но это уже немного другой уровень и думаю новичкам это будет посложнее. Но эксперименты с нейронными сетями я чуть позже повторю, чтобы тоже проверить верность и повторяемость данного руководства.

Немаловажным фактором на качество распознавания оказывает получаемое изображение с веб-камеры. Предложение использовать 1/4 изображения (сжатие его) приводит только к ухудшению - моё лицо алгоритм распознать так и не смог. Для повышения качества предлагают использовать MTCNN face detector (пример использования), либо что-нибудь посложнее из абзаца выше.

Другая интересная особенность - таймеры в Питоне. Я, опять же, признаю, что ни разу до этого не было нужды в них, но все статьях сводится к тому, чтобы ставить поток в sleep(кол-во секунд). А если мне нужно сделать так, чтобы основной поток был в работе, а по истечению n-ое количества секунд не было активности, то выполнялась моя функция? Использовать демонов (daemon)? Так это не совсем то, что нужно. Писать отдельную программу, которая взаимодействует с другой? Возможно, но единство программы пропадает.

Заключение

На своём примере могу точно сказать - не все руководства одинаково полезны и простая задача может перерасти в сложную. Я понимаю, что большинство материалов является переводом/простым копированием документации. Но если ты пишешь руководство, то проверь хотя бы на тестовой системе, что написанные тобой действия точно повторимы. Пробуйте, экспериментируйте - это хороший повод изучить и узнать новое.

P.S. Предлагаю вам, читатели, обсудить в комментариях статью - ваши идеи, замечания, уточнения.

Источник: https://habr.com/ru/post/563124/


Интересные статьи

Интересные статьи

Image (c) Opensource.com Новая порция инсайтов, мероприятий, книжек и шпаргалок. Оставайтесь с нами – станьте частью DevNation! Читать дальше: Новые функции...
На Ютубе хватает роликов, записанных без особой цели: кадры с отдыха, встреч с семьей или каких-то других событий из жизни. Многие из них выглядят так, словно были выстав...
Всем привет! Сегодня мы будем пытаться собрать Python 1.0.1 (1994 год) на современном железе при помощи современного компилятора. Даже если Вы, как Python разработчик, никогда не компилир...
TL;DR В этой статье мы начнем решать проблему того, как сделать печатные ссылки в книгах или журналах кликабельными используя камеру смартфона. С помощью TensorFlow 2 Object Detection...
Привет, Хабр. Читатели старшего поколения, заставшие дома радиоприемники средних, длинных и коротких волн, наверное помнят, что разные длины волн по-разному распространяются в различно...