+7-960-0655211 (Билайн)
+7-987-4207734 (МТС)

интернет-магазин
доставка по России и СНГ
работаем с 2010 года

Камера с механизмом поворота/наклона и управлением по локальной сети

Камера с механизмом поворота/наклона и управлением по локальной сети

В предыдущем проекте было рассмотрено, как сделать механизм поворота/наклона для камеры и управлять сервоприводами. Теперь мы рассмотрим, как реализовать трансляцию видео по сети, а с помощью Flask и Python управление. На Web-странице будет отображаться видео с камеры, а кнопками с запрограммированными углами будем задавать нужное положение камеры.

01.gif

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

Ниже приведена блок-схема, как это работает:


02.png

Установка Pi-камеры

03.jpg

Выключив питание Raspberry Pi, подключаем камеру:


04.jpg


Включаем питание Raspberry Pi и запускаем конфигурационную утилиту:


05.png


На закладке "Интерфейсы" ("Interfaces") проверяем, включена ли камера:


06.jpg


Если камера не была включена, включаем и нажимаем кнопку "OK". После этого перезапускаем Raspberry Pi. Простой способ проверить, заработала ли камера, это в консоли выполнить команду:

raspistill -o /Desktop/image.png

После её выполнения на рабочем столе должна появиться файл "image.png". Откройте его и если всё в порядке, в нём будет изображение с камеры.

Установка Flask

Есть несколько способов для организации потокового видео. Из всех перепробованных, наиболее удобным и простым для меня оказался вариант с использованием Flask.

Для установки Flask, в консоли введите команду:

sudo apt-get install python3-flask

Создание окружение web-сервера:

Все файлы проекта можете скачать из репозитария camWebServer, например в папку документов:


07.jpg

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)


Приведенный выше скрипт транслирует видео с камеры на страницу 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="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.

Если всё работает, страница будет выглядеть примерно так:


08.gif



Механизм наклона/поворота

Теперь, когда у нас работает камера и Flask WebServer транслирует видео, можно переходить к механизму наклона/поворота для удаленного позиционирования камеры. В предыдущем проекте это детально было рассмотрено.

Управление положением камеры с помощью кнопок на Web-странице


09.png

Для дальнейшей работы в папке документов я создам новую папку PanTiltControl1:


10.jpg

Скачать файлы можно из репозитория по ссылке.

Давайте посмотрим на 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)
os.system("python3 angleServoCtrl.py " + str(panPin) + " " + str(panServoAngle))

Т.к. в данном случае PanPin равняется 27, а panServoAngle равняется 30, то скрипт сгенерирует команду:

python3 angleServoCtrl 27 30

Т.е. в этом коде происходит тоже самое, что мы до этого делали в консоли, когда проверяли работоспособность.

Обратите внимание, что нам не нужно использовать "sudo", т.к. приложение уже было запущено с использованием "sudo".

На следующем видео показано, как всё работает и в консоли справа внизу, какие GET-запросы получает сервер на Raspberry:



Использование кнопок увеличения и уменьшения угла


11.png

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

Пример 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 == '+':
panServoAngle = panServoAngle + 10
else:
panServoAngle = panServoAngle - 10

Если передано "-", т.е. переменная "angle" будет равняться "-", мы уменьшим на 10 значение переменной panServoAngle и передадим этот параметр нашей команде. К примеру, если переменная panServoAngle равнялась 90, новое значение будет 80. Затем, как и ранее выполниться:

os.system("python3 angleServoCtrl.py " + str(panPin) + " " + str(panServoAngle))

Таким образом приложение сгенерирует команду:

python3 angleServoCtrl 27 80

Демонстрация, как это работает:


12.gif

Использование отправки "POST"-запроса


13.png

Иногда для ввода данных может быть удобным использовать не кнопки, а редактируемые текстовые поля, в которых вводятся данные. Так же можно реализовать отправку данных не по отдельности для каждого сервопривода, а одним запросом. Это можно реализовать и с помощью отправки 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)

В условиях проверяется, отличаются ли полученные значения от тех, что хранятся в переменных. Если значение угла было изменено, сохраняются новые значения и вызывается скрипт angleServoCtrl.py для установки сервоприводов в новое положение.




Автор: Marcelo Rovai
Перевод и адаптация: RobotoTehnika.ru