Compare commits

..

No commits in common. "dacad4db10fe1c5a0a5b4969737635d33fbf7f39" and "2aa965cc833a200d299bf63c53c3dc81f3543237" have entirely different histories.

14 changed files with 259 additions and 302 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

4
.gitignore vendored
View File

@ -1,4 +0,0 @@
__pycache__/
test.py
static/images/*
venv/

View File

@ -1,38 +1,79 @@
# Flask Camera Live Stream
# Make you own Raspberry Pi Camera Stream
[Original Project](https://github.com/EbenKouao/pi-camera-stream-flask)
Create your own live stream from a Raspberry Pi using the Pi camera module. Build your own applications from here.
## Usage
### Creating the virtual environment
## How it works
The Pi streams the output of the camera module over the web via Flask. Devices connected to the same network would be able to access the camera stream via
```
pip install venv
python -m venv venv
<raspberry_pi_ip:5000>
```
### Activate the virtual environment
## Screenshots
| ![Setup](readme/pi-stream-client.jpg) | ![Live Pi Camera Stream](readme/pi-stream-screen-capture.jpg) |
|---|---|
| Pi Setup | Pi - Live Stream |
*Windows (CMD)*
## Preconditions
* Raspberry Pi 4, 2GB is recommended for optimal performance. However you can use a Pi 3 or older, you may see a increase in latency.
* Raspberry Pi 4 Camera Module or Pi HQ Camera Module (Newer version)
* Python 3 recommended.
## Library dependencies
Install the following dependencies to create camera stream.
```
venv\Scripts\activate.bat
```
sudo apt-get install libatlas-base-dev
sudo apt-get install libjasper-dev
sudo apt-get install libqtgui4
sudo apt-get install libqt4-test
sudo apt-get install libhdf5-dev
*Linux / Mac OS*
sudo pip3 install flask
sudo pip3 install numpy
sudo pip3 install opencv-contrib-python
sudo pip3 install imutils
sudo pip3 install opencv-python
```
source venv/bin/activate
```
### Installing the dependencies
pip3 install opencv-python
## Step 1 Cloning Raspberry Pi Camera Stream
Open up terminal and clone the Camera Stream repo:
```
pip install -r requirements.txt
cd /home/pi
git clone https://github.com/EbenKouao/pi-camera-stream-flask.git
```
### Running the app (not recommended for production)
## Step 2 Launch Web Stream
Note: Creating an Autostart of the main.py script is recommended to keep the stream running on bootup.
```bash cd modules
sudo python3 /home/pi/pi-camera-stream-flask/main.py
```
## Step 3 Autostart your Pi Stream
Optional: A good idea is running your Pi Camera stream at Pi boot up. This removes the need to re-run the script every time you want to create the stream.
You can do this by going adding the boot up code the .bashrc file.
Via the Desktop GUI - right click in your /home/pi/ directory -> show hidden -> open .bashrc and add the code.
Or alternatively access via terminal:
```
python main.py
```
sudo nano /home/pi/.bashrc
```
Go the end of the and add the following (from above):
```
sudo python3 /home/pi/pi-camera-stream-flask/main.py
```
This would cause the following terminal command to auto-start upon Raspberry Pi boot up.
## Download Beta image of Raspberry Pi Camera Stream
Any troubles installing, try out the already compiled Raspberry Pi (Raspbian OS) Image of [Raspberry Pi Camera Stream](https://smartbuilds.io).
![Raspbian Camera Stream Image](img/readme/[].png)

View File

@ -1,17 +1,21 @@
#Modified by smartbuilds.io
#Date: 27.09.20
#Desc: This scrtipt script..
import cv2
from datetime import datetime
from imutils.video.pivideostream import PiVideoStream
import imutils
import time
import numpy as np
class VideoCamera(object):
def __init__(self, flip=False, file_type=".jpg", photo_string="stream_photo"):
self.vs = cv2.VideoCapture(0)
def __init__(self, flip = False):
self.vs = PiVideoStream().start()
self.flip = flip
self.file_type = file_type
self.photo_string = photo_string
self.exposure_value = self.vs.get(cv2.CAP_PROP_EXPOSURE)
time.sleep(2.0)
def __del__(self):
self.vs.release()
self.vs.stop()
def flip_if_needed(self, frame):
if self.flip:
@ -19,26 +23,6 @@ class VideoCamera(object):
return frame
def get_frame(self):
ret, frame = self.vs.read()
if not ret:
return None
frame = self.flip_if_needed(frame)
ret, jpeg = cv2.imencode(self.file_type, frame)
return jpeg.tobytes()
def take_picture(self):
ret, frame = self.vs.read()
if not ret:
return
frame = self.flip_if_needed(frame)
today_date = datetime.now().strftime("%m%d%Y-%H%M%S")
cv2.imwrite(str("static/images/" + self.photo_string + "_" + today_date + self.file_type), frame)
def set_exposure(self, exposure_value):
self.vs.set(cv2.CAP_PROP_EXPOSURE, exposure_value)
self.exposure_value = exposure_value
def get_exposure(self):
return self.exposure_value
frame = self.flip_if_needed(self.vs.read())
ret, jpeg = cv2.imencode('.jpg', frame)
return jpeg.tobytes()

52
main.py
View File

@ -1,26 +1,25 @@
from flask import Flask, render_template, Response
#Modified by smartbuilds.io
#Date: 27.09.20
#Desc: This web application serves a motion JPEG stream
# main.py
# import the necessary packages
from flask import Flask, render_template, Response, request
from camera import VideoCamera
from util import list_files_in_dir, generate_url
import time
import threading
import os
camera = VideoCamera(flip=False)
pi_camera = VideoCamera(flip=False) # flip pi camera if upside down.
# App Globals (do not edit)
app = Flask(__name__)
@app.route('/')
def index():
return render_template('index.html')
@app.route('/images')
def images_view():
file_directory = 'images'
url_list = list()
for file in list_files_in_dir('static/'+file_directory):
url_list.append(generate_url(file_directory, file))
return render_template('images.html', urls=url_list)
return render_template('index.html') #you can customze index.html here
def gen(camera):
#get camera frame
while True:
frame = camera.get_frame()
yield (b'--frame\r\n'
@ -28,25 +27,12 @@ def gen(camera):
@app.route('/video_feed')
def video_feed():
return Response(gen(camera),
return Response(gen(pi_camera),
mimetype='multipart/x-mixed-replace; boundary=frame')
@app.route('/picture')
def take_picture():
camera.take_picture()
return "None"
@app.route('/moreexposure')
def more_exposure():
exposure = camera.get_exposure()
camera.set_exposure(exposure + 1)
return "None"
@app.route('/lessexposure')
def less_exposure():
exposure = camera.get_exposure()
camera.set_exposure(exposure - 1)
return "None"
if __name__ == '__main__':
app.run(host='0.0.0.0', debug=False)
app.run(host='0.0.0.0', debug=False)

BIN
readme/.DS_Store vendored Normal file

Binary file not shown.

BIN
readme/pi-stream-client.jpg Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@ -1,10 +0,0 @@
blinker==1.6.2
click==8.1.7
colorama==0.4.6
Flask==2.3.3
itsdangerous==2.1.2
Jinja2==3.1.2
MarkupSafe==2.1.3
numpy==1.25.2
opencv-python==4.8.0.76
Werkzeug==2.3.7

View File

@ -1,45 +0,0 @@
$(function() {
$('a#take-picture').on('click', function(e) {
e.preventDefault()
$.getJSON('/picture',
function(data) {
//do nothing
});
return false;
});
});
$(function() {
$('a#more-exposure').on('click', function(e) {
e.preventDefault()
$.getJSON('/moreexposure',
function(data) {
//do nothing
});
return false;
});
});
$(function() {
$('a#less-exposure').on('click', function(e) {
e.preventDefault()
$.getJSON('/lessexposure',
function(data) {
//do nothing
});
return false;
});
});
$(function() {
$('a#copy-video-stream-url').on('click', function(e) {
const textArea = document.createElement("textarea");
const video_stream_path = document.getElementById("bg").src;
textArea.value = video_stream_path;
document.body.appendChild(textArea);
textArea.select();
document.execCommand("copy");
document.body.removeChild(textArea);
return false;
});
});

View File

@ -1,84 +0,0 @@
body {
margin: 0;
padding: 0;
width: 100vw;
height: 100vh;
overflow: hidden;
background-color: black;
font-family: Arial, Helvetica, sans-serif;
}
.navbar {
overflow: hidden;
position: fixed;
bottom: 0;
width: 100%;
margin: auto;
text-align: center;
background-color: black;
opacity:0.6;
}
.navbar div {
display: inline-block;
}
.navbar a {
float: left;
display: block;
color: #f2f2f2;
text-align: center;
padding: 14px 16px;
text-decoration: none;
font-size: 17px;
}
.navbar a.active {
background-color: #4CAF50;
color: white;
}
.main {
padding: 16px;
margin-bottom: 30px;
}
i.fa {
display: inline-block;
border-radius: 60px;
box-shadow: 0px 0px 2px #888;
padding: 0.5em 0.6em;
background: blue;
color: white;
}
img {
display: block;
margin-left: auto;
margin-right: auto;
width: 35%
}
button {
background-color: Transparent;
background-repeat:no-repeat;
border: none;
cursor:pointer;
overflow: hidden;
outline:none;
}
.camera-bg {
display: block;
margin: auto;
max-height: 100vh;
max-width: 100vh;
width: 100%;
background-position: center;
background-repeat: no-repeat;
background-size: cover;
background-attachment: fixed;
}

View File

@ -1,13 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Camera Live Feed - Images</title>
</head>
<body>
{% for url in urls %}
<img src="{{ url }}" alt="Taken Image">
{% endfor %}
</body>
</html>

View File

@ -1,62 +1,177 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Camera Live Feed</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
</head>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body {
margin: 0;
font-family: Arial, Helvetica, sans-serif;
}
<body>
<div class="main">
<img class="camera-bg" id="bg" class="center" src="{{ url_for('video_feed') }}">
.navbar {
overflow: hidden;
position: fixed;
bottom: 0;
width: 100%;
margin: auto;
background-color: black;
opacity:0.6;
}
.navbar a {
float: left;
display: block;
color: #f2f2f2;
text-align: center;
padding: 14px 16px;
text-decoration: none;
font-size: 17px;
}
.navbar a:hover {
}
.navbar a.active {
background-color: #4CAF50;
color: white;
}
.main {
padding: 16px;
margin-bottom: 30px;
}
.camera-movement{
float: none;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.lights-button{
float: right;
}
i.fa {
display: inline-block;
border-radius: 60px;
box-shadow: 0px 0px 2px #888;
padding: 0.5em 0.6em;
}
img {
display: block;
margin-left: auto;
margin-right: auto;
width: 35%
}
button {
background-color: Transparent;
background-repeat:no-repeat;
border: none;
cursor:pointer;
overflow: hidden;
outline:none;
}
//CSS
.camera-bg {
position: fixed;
top: 0;
left: 0;
/* Preserve aspet ratio */
min-width: 100%;
min-height: 100%;
/* Full height */
height: 100%;
/* Center and scale the image nicely */
background-position: center;
background-repeat: no-repeat;
background-size: cover;
}
.top-right-logo {
position: absolute;
top: 3%;
left: 2%;
font-size: 38px;
color: white;
opacity: 0.5;
}
body {
margin: 0;
padding: 0;
width: 100vw;
height: 100vh;
overflow: hidden;
background-color: black;
}
</style>
</head>
<title>Make - PiStream</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<body>
<div class="main" id="newpost">
<img class="camera-bg" style="width: 100%; height:80%; background-attachment: fixed;" id="bg" class="center" src="{{ url_for('video_feed') }}">
<!--<img class="camera-bg" style="width: 100%; height:80%; background-attachment: fixed;" id="bg" class="center" src="https://www.psdbox.com/wp-content/uploads/2011/01/security-camera-photoshop-effect.jpg">-->
</div>
<div class="top-right-logo">
<a></a>Raspberry Pi - Camera Stream </a>
</div>
<div class="navbar">
<div class="ignoreCall">
<a id=decline class="but_def">
<button id="button">
<i style="background: red; color: white;" class="fa fa-times fa-2x" aria-hidden="true"></i>
</button>
</a>
</div>
<div class="navbar">
<div>
<a href="/images" title="Gallery">
<button>
<i class="fa fa-picture-o fa-2x" aria-hidden="true"></i>
</button>
</a>
</div>
</div>
<div>
<a href="#" id="take-picture" title="Take a picture">
<button>
<i class="fa fa-camera fa-2x" aria-hidden="true"></i>
</button>
</a>
</div>
<div>
<a href="#" id="more-exposure" title="More exposure">
<button>
<i class="fa fa-plus fa-2x" aria-hidden="true"></i>
</button>
</a>
</div>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<div>
<a href="#" id="less-exposure" title="Less exposure">
<button>
<i class="fa fa-minus fa-2x" aria-hidden="true"></i>
</button>
</a>
</div>
<script type="text/javascript">
var button = document.getElementById('button');
<div>
<a href="#" id="copy-video-stream-url" title="Copy the video stream url">
<button>
<i class="fa fa-clipboard fa-2x" aria-hidden="true"></i>
</button>
</a>
</div>
button.onclick = function() {
var div = document.getElementById('newpost');
if (div.style.display !== 'none') {
div.style.display = 'none';
}
else {
div.style.display = 'block';
}
};
</script>
</div>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script type="text/javascript" src="{{ url_for('static', filename='script.js') }}"></script>
</body>
</body>
</html>

13
util.py
View File

@ -1,13 +0,0 @@
import os
from flask import url_for
def list_files_in_dir(directory_path: str) -> list[str]:
file_list = os.listdir(directory_path)
file_list = [file for file in file_list if os.path.isfile(os.path.join(directory_path, file))]
return file_list
def generate_url(directory_path: str, file_name: str) -> str:
url = url_for('static', filename=f'{directory_path}/{file_name}')
return url