diff --git a/.gitignore b/.gitignore index 747fae6..e2e18de 100644 --- a/.gitignore +++ b/.gitignore @@ -1,50 +1,4 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. - -# dependencies -/node_modules -/.pnp -.pnp.js - -# testing -/coverage - -# next.js -/.next/ -/out/ - -# production -/build - -# misc -.DS_Store -*.pem - -# debug -npm-debug.log* -yarn-debug.log* -yarn-error.log* - -# local env files -env -.env/ -.env -.env.local -.env.development.local -.env.test.local -.env.production.local -dev.env - -# vercel -.vercel - -#logs -*.log - - -#_pycache __pycache__/ - -#image_files -*.jpg -*.jpeg -*.png +test.py +static/images/* +venv/ \ No newline at end of file diff --git a/README.md b/README.md index d9bb6a4..7fd480d 100644 --- a/README.md +++ b/README.md @@ -1,88 +1,38 @@ -# Make you own Raspberry Pi Camera Stream +# Flask Camera Live Stream -Create your own live stream from a Raspberry Pi using the Pi camera module. Build your own applications from here. +[Original Project](https://github.com/EbenKouao/pi-camera-stream-flask) -## 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 +## Usage + +### Creating the virtual environment ``` - +pip install venv +python -m venv venv ``` -## Screenshots -| ![Setup](readme/pi-stream-client.jpg) | ![Live Pi Camera Stream](readme/pi-stream-screen-capture.jpg) | -| ------------------------------------- | ------------------------------------------------------------- | -| Pi Setup | Pi - Live Stream | +### Activate the virtual environment -## 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. +*Windows (CMD)* ``` -sudo apt-get update -sudo apt-get upgrade - -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 - -sudo pip3 install flask -sudo pip3 install numpy -sudo pip3 install opencv-contrib-python -sudo pip3 install imutils -sudo pip3 install opencv-python - +venv\Scripts\activate.bat ``` -Note: This installation of opencv may take a while depending on your pi model. - -OpenCV alternate installation (in the event of failed opencv builds): +*Linux / Mac OS* ``` -sudo apt-get install libopencv-dev python3-opencv +source venv/bin/activate ``` -## Step 1 – Cloning Raspberry Pi Camera Stream -Open up terminal and clone the Camera Stream repo: +### Installing the dependencies ``` -cd /home/pi -git clone https://github.com/EbenKouao/pi-camera-stream-flask.git +pip install -r requirements.txt ``` -## 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 to make the the camera stream auto start at bootup of your pi. You will now not need to re-run the script every time you want to create the stream. You can do this by going editing the /etc/profile to: +### Running the app (not recommended for production) ``` -sudo nano /etc/profile -``` - -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 each time the Raspberry Pi boots up. This in effect creates a headless setup - which would be accessed via SSH. -Note: make sure SSH is enabled. - -## More Projects / Next Steps -View the latest Build: [Pi Smart Cam with Motion Sensor](https://github.com/EbenKouao/pi-smart-cam) - -Alternatively, view more projects that build on the Pi Camera on [smartbuilds.io](https://smartbuilds.io). - +python main.py +``` \ No newline at end of file diff --git a/camera.py b/camera.py index f70338e..ad164fc 100644 --- a/camera.py +++ b/camera.py @@ -1,25 +1,17 @@ -#Modified by smartbuilds.io -#Date: 27.09.20 -#Desc: This scrtipt script.. - -import cv2 as cv -from imutils.video.pivideostream import PiVideoStream -import imutils -import time +import cv2 from datetime import datetime import numpy as np class VideoCamera(object): - def __init__(self, flip = False, file_type = ".jpg", photo_string= "stream_photo"): - # self.vs = PiVideoStream(resolution=(1920, 1080), framerate=30).start() - self.vs = PiVideoStream().start() - self.flip = flip # Flip frame vertically - self.file_type = file_type # image type i.e. .jpg - self.photo_string = photo_string # Name to save the photo - time.sleep(2.0) + def __init__(self, flip=False, file_type=".jpg", photo_string="stream_photo"): + self.vs = cv2.VideoCapture(0) + self.flip = flip + self.file_type = file_type + self.photo_string = photo_string + self.exposure_value = self.vs.get(cv2.CAP_PROP_EXPOSURE) def __del__(self): - self.vs.stop() + self.vs.release() def flip_if_needed(self, frame): if self.flip: @@ -27,14 +19,26 @@ class VideoCamera(object): return frame def get_frame(self): - frame = self.flip_if_needed(self.vs.read()) - ret, jpeg = cv.imencode(self.file_type, frame) - self.previous_frame = jpeg + 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() - # Take a photo, called by camera button def take_picture(self): - frame = self.flip_if_needed(self.vs.read()) - ret, image = cv.imencode(self.file_type, frame) - today_date = datetime.now().strftime("%m%d%Y-%H%M%S") # get current time - cv.imwrite(str(self.photo_string + "_" + today_date + self.file_type), frame) + 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 diff --git a/main.py b/main.py index 603aa34..e32da11 100644 --- a/main.py +++ b/main.py @@ -1,23 +1,26 @@ -#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, send_from_directory +from flask import Flask, render_template, Response from camera import VideoCamera -import os +from util import list_files_in_dir, generate_url -pi_camera = VideoCamera(flip=False) # flip pi camera if upside down. +camera = VideoCamera(flip=False) -# App Globals (do not edit) app = Flask(__name__) @app.route('/') def index(): - return render_template('index.html') #you can customze index.html here + 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) def gen(camera): - #get camera frame while True: frame = camera.get_frame() yield (b'--frame\r\n' @@ -25,15 +28,25 @@ def gen(camera): @app.route('/video_feed') def video_feed(): - return Response(gen(pi_camera), + return Response(gen(camera), mimetype='multipart/x-mixed-replace; boundary=frame') -# Take a photo when pressing camera button @app.route('/picture') def take_picture(): - pi_camera.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) \ No newline at end of file diff --git a/readme/.DS_Store b/readme/.DS_Store deleted file mode 100644 index 1be2f78..0000000 Binary files a/readme/.DS_Store and /dev/null differ diff --git a/readme/pi-stream-client.jpg b/readme/pi-stream-client.jpg deleted file mode 100755 index f4be707..0000000 Binary files a/readme/pi-stream-client.jpg and /dev/null differ diff --git a/readme/pi-stream-screen-capture.jpg b/readme/pi-stream-screen-capture.jpg deleted file mode 100644 index dfc42bc..0000000 Binary files a/readme/pi-stream-screen-capture.jpg and /dev/null differ diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..902fe0a --- /dev/null +++ b/requirements.txt @@ -0,0 +1,10 @@ +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 diff --git a/static/favicon.ico b/static/favicon.ico deleted file mode 100644 index dcdebc4..0000000 Binary files a/static/favicon.ico and /dev/null differ diff --git a/static/script.js b/static/script.js new file mode 100644 index 0000000..21b4865 --- /dev/null +++ b/static/script.js @@ -0,0 +1,45 @@ +$(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; + }); +}); \ No newline at end of file diff --git a/static/style.css b/static/style.css new file mode 100644 index 0000000..1ae085e --- /dev/null +++ b/static/style.css @@ -0,0 +1,84 @@ +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; +} \ No newline at end of file diff --git a/templates/images.html b/templates/images.html new file mode 100644 index 0000000..fd18d28 --- /dev/null +++ b/templates/images.html @@ -0,0 +1,13 @@ + + + + + + Camera Live Feed - Images + + + {% for url in urls %} + Taken Image + {% endfor %} + + \ No newline at end of file diff --git a/templates/index.html b/templates/index.html index 07bc3e7..f86dfad 100644 --- a/templates/index.html +++ b/templates/index.html @@ -1,214 +1,62 @@ - - - - - - -Make - PiStream - - - - - -
- - - -
- - - - -