В предыдущем проекте было рассмотрено, как сделать механизм поворота/наклона для камеры и управлять сервоприводами. Теперь мы рассмотрим, как реализовать трансляцию видео по сети, а с помощью Flask и Python управление. На Web-странице будет отображаться видео с камеры, а кнопками с запрограммированными углами будем задавать нужное положение камеры.
Также будут рассмотрены альтернативные варианты управления положением камеры по сети.
Ниже приведена блок-схема, как это работает:
Выключив питание Raspberry Pi, подключаем камеру:
Включаем питание Raspberry Pi и запускаем конфигурационную утилиту:
На закладке "Интерфейсы" ("Interfaces") проверяем, включена ли камера:
Если камера не была включена, включаем и нажимаем кнопку "OK". После этого перезапускаем Raspberry Pi. Простой способ проверить, заработала ли камера, это в консоли выполнить команду:
raspistill -o /Desktop/image.pngПосле её выполнения на рабочем столе должна появиться файл "image.png". Откройте его и если всё в порядке, в нём будет изображение с камеры.
Есть несколько способов для организации потокового видео. Из всех перепробованных, наиболее удобным и простым для меня оказался вариант с использованием Flask.
Для установки Flask, в консоли введите команду:
sudo apt-get install python3-flaskВсе файлы проекта можете скачать из репозитария camWebServer, например в папку документов:
appCam.py это скрипт в котором с помощью Flask реализуется рендеринг потокового видео:
from flask import Flask, render_template, Response # Raspberry Pi camera module (requires picamera package, developed by Miguel Grinberg) from camera_pi import Camera app = Flask(__name__) @app.route('/') def index(): """Video streaming home page.""" return render_template('index.html') def gen(camera): """Video streaming generator function.""" while True: frame = camera.get_frame() yield (b'--frame\r\n' b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n') @app.route('/video_feed') def video_feed(): """Video streaming route. Put this in the src attribute of an img tag.""" return Response(gen(Camera()), mimetype='multipart/x-mixed-replace; boundary=frame') if __name__ == '__main__': app.run(host='0.0.0.0', port =80, debug=True, threaded=True)
<html> <head> <title>MJRoBot Lab Live Streaming</title> <link rel="stylesheet" href='../static/style.css'/> </head> <body> <h1>MJRoBot Lab Live Streaming</h1> <h3><img src="{{ url_for('video_feed') }}" width="90%"></h3> <hr> </body> </html>
Наиболее важная строка файла index.html:
<img src="{{ url_for('video_feed') }}" width="50%">В ней для Web-страницы задаётся источник для видео.
Для проверки работоспособности, откройте консоль, в ней перейдите в папку camWebServer и запустите скрипт appCam.py:
sudo python3 appCam.pyВ браузере с устройства из Вашей локальной сети, откройте страницу с адресом Raspberry Pi (для примера, в моём случае это http://10.0.1.27). Посмотреть адрес можно в консоли с помощью команды ifconfig. При подключении по WiFi это адрес будет указан в информаци для интерфейса wlan0.
Если всё работает, страница будет выглядеть примерно так:
Для дальнейшей работы в папке документов я создам новую папку PanTiltControl1:
Скачать файлы можно из репозитория по ссылке.
Давайте посмотрим на index.html:
<html> <head> <title>MJRoBot Lab Live Streaming</title> <link rel="stylesheet" href='../static/style.css'/> </head> <body> <h1>MJRoBot Lab Live Streaming</h1> <h3><img src="{{ url_for('video_feed') }}" width="80%"></h3> <hr> <h4> PAN: <a href="/pan/30"class="button">30</a> <a href="/pan/45"class="button">45</a> <a href="/pan/60"class="button">60</a> <a href="/pan/75"class="button">75</a> <a href="/pan/90"class="button">90</a> <a href="/pan/105"class="button">105</a> <a href="/pan/120"class="button">120</a> <a href="/pan/135"class="button">135</a> <a href="/pan/150"class="button">150</a> ==> Angle: [ {{ panServoAngle }} ] </h4> <h4> TILT: <a href="/tilt/30"class="button">30</a> <a href="/tilt/45"class="button">45</a> <a href="/tilt/60"class="button">60</a> <a href="/tilt/75"class="button">75</a> <a href="/tilt/90"class="button">90</a> <a href="/tilt/105"class="button">105</a> <a href="/tilt/120"class="button">120</a> <a href="/tilt/135"class="button">135</a> <a href="/tilt/150"class="button">150</a> ==> Angle: [ {{ tiltServoAngle }} ] </h4> <hr> </body> </html>
Файл Index.html был создан из предыдущего файла, который использовался при передаче нашего видео. Новые строки в нём это для вывода кнопок на Web-странице. Рассмотрим одну из из таких строк:
<a href="/pan/30"class="button">30</a>Это простая HTML гиперссылка, которую мы стилизовали под кнопку (стиль находится в файле style.css). При нажатии на эту кнопку, браузер отправляет запрос вида "GET /<servo>/<angle>", в котором указывает какому сервоприводу на какой угол нужно провернуться. В данном случае будет передано "GET /pan/30", т.е. что для сервопривода поворота ("pan", панорамирование) установить угол в 30 градусов. Эти параметры будут переданы скрипту веб-сервера (appCamPanTilt1.py).
Давайте посмотрим на часть кода скрипта appCamPanTilt1.py:
@app.route("/<servo>/<angle>") def move(servo, angle): global panServoAngle global tiltServoAngle if servo == 'pan': panServoAngle = int(angle) os.system("python3 angleServoCtrl.py " + str(panPin) + " " + str(panServoAngle)) if servo == 'tilt': tiltServoAngle = int(angle) os.system("python3 angleServoCtrl.py " + str(tiltPin) + " " + str(tiltServoAngle))
В этом примере переменная "servo" равна "pan", т.е. будет срабатывать первое условие и выполнятся две следующие строчки кода:
panServoAngle = int(angle)Т.к. в данном случае PanPin равняется 27, а panServoAngle равняется 30, то скрипт сгенерирует команду:
python3 angleServoCtrl 27 30Т.е. в этом коде происходит тоже самое, что мы до этого делали в консоли, когда проверяли работоспособность.
Обратите внимание, что нам не нужно использовать "sudo", т.к. приложение уже было запущено с использованием "sudo".
На следующем видео показано, как всё работает и в консоли справа внизу, какие GET-запросы получает сервер на Raspberry:
Иногда вместо большого количества кнопок, которые задают заранее установленные углы, будет удобней использовать четыре кнопки, которые будут увеличивать и уменьшать угол.
Пример PanTiltControl2 можно скачать по ссылке. В этом примере изменены некоторые файлы, рассмотрим эти изменения. Новый файл index.html:
<html> <head> <title>MJRoBot Lab Live Streaming</title> <link rel="stylesheet" href='../static/style.css'/> </head> <body> <h1>MJRoBot Lab Live Streaming</h1> <h3><img src="{{ url_for('video_feed') }}" width="80%"></h3> <hr> <h4> PAN Angle: <a href="/pan/-"class="button">-</a> [ {{ panServoAngle }} ] <a href="/pan/+"class="button">+</a> </h4> <h4> TILT Angle: <a href="/tilt/-"class="button">-</a> [ {{ tiltServoAngle }} ] <a href="/tilt/+"class="button">+</a> </h4> <hr> </body> </html>
Он очень похож на предыдущий. Множество строк для кнопок здесь заменены всего-лишь на пару строк, в которых выводится 4 кнопки:
Pan [+], Pan [-], Tilt [+] и Tilt [-]Давайте посмотрим на одну из этих четырёх кнопок:
<a href="/pan/-"class="button">-</a>Это также простая HTML гиперссылка, которую мы стилизовали под кнопку. При нажатии на эту кнопку, браузер отправляет запрос "GET /<servo>/<Угол увеличить или уменьшить>". Как и ранее, <servo> задаёт сервопривод. Вместо указания угла, теперь мы передаём "+" или "-", что бы сообщить, нужно уменьшить или увеличить угол. Эти параметры будут переданы в приложение веб-сервера (appCamPanTilt2.py).
Давайте посмотрим на часть кода скрипта appCamPanTilt2.py:
@app.route("/<servo>/<angle>") def move(servo, angle): global panServoAngle global tiltServoAngle if servo == 'pan': if angle == '+': panServoAngle = panServoAngle + 10 else: panServoAngle = panServoAngle - 10 os.system("python3 angleServoCtrl.py " + str(panPin) + " " + str(panServoAngle)) if servo == 'tilt': if angle == '+': tiltServoAngle = tiltServoAngle + 10 else: tiltServoAngle = tiltServoAngle - 10 os.system("python3 angleServoCtrl.py " + str(tiltPin) + " " + str(tiltServoAngle)) templateData = { 'panServoAngle' : panServoAngle, 'tiltServoAngle' : tiltServoAngle } return render_template('index.html', **templateData)
В этом примере "servo" равно "pan", т.е. выполнится первое условие, в котором уже будет проверяться, нужно увеличить или уменьшить угол для сервопривода:
if angle == '+':Если передано "-", т.е. переменная "angle" будет равняться "-", мы уменьшим на 10 значение переменной panServoAngle и передадим этот параметр нашей команде. К примеру, если переменная panServoAngle равнялась 90, новое значение будет 80. Затем, как и ранее выполниться:
os.system("python3 angleServoCtrl.py " + str(panPin) + " " + str(panServoAngle))Таким образом приложение сгенерирует команду:
python3 angleServoCtrl 27 80Демонстрация, как это работает:
Иногда для ввода данных может быть удобным использовать не кнопки, а редактируемые текстовые поля, в которых вводятся данные. Так же можно реализовать отправку данных не по отдельности для каждого сервопривода, а одним запросом. Это можно реализовать и с помощью отправки Get-запроса, но в данном случае мы рассмотрим реализацию с использованием POST-запроса.
Пример PanTiltControl3 можно скачать по ссылке. В этом примере изменены некоторые файлы, рассмотрим эти изменения.
Давайте посмотрим на новый index.html:
<html> <head> <title>MJRoBot Lab Live Streaming</title> <link rel="stylesheet" href='../static/style.css'/> </head> <body> <h1>MJRoBot Lab Live Streaming</h1> <h3><img src="{{ url_for('video_feed') }}" width="80%"></h3> <p> Enter Pan Tilt Servo Angle: <form method="POST"> PAN: <input type="text" name="panServoAngle" value= {{panServoAngle}} size="3"> TILT: <input type="text" name="tiltServoAngle" value= {{tiltServoAngle}} size="3"> <input type="submit"> </form> </p> <hr> </body> </html>
Index.html сейчас немного отличается от предыдущего. Здесь создана форма ("form") с методом "POST". В этой форме есть два поля для ввода значений угла поворота и наклона. Эти параметры будут переданы приложению веб-сервера (appCamPanTilt3.py) при нажатии кнопки "Отправить" ("Submit").
Давайте посмотрим на часть кода в скрипте appCamPanTilt3.py:
@app.route('/', methods=['POST']) def my_form_post(): global panServoAngle global tiltServoAngle panNewAngle = int(request.form['panServoAngle']) if (panNewAngle != panServoAngle): panServoAngle = panNewAngle os.system("python3 angleServoCtrl.py " + str(panPin) + " " + str(panServoAngle)) tiltNewAngle = int(request.form['tiltServoAngle']) if (tiltNewAngle != tiltServoAngle): tiltServoAngle = tiltNewAngle os.system("python3 angleServoCtrl.py " + str(tiltPin) + " " + str(tiltServoAngle)) templateData = { 'panServoAngle' : panServoAngle, 'tiltServoAngle' : tiltServoAngle } return render_template('index.html', **templateData)
Автор: Marcelo Rovai
Перевод и адаптация: RobotoTehnika.ru