-2

I'm trying to run this website that takes using flask that takes a picture when u press next but it only works on the first time I go to that URL. When I get redirected to the same url then it no longer works.

Please help I've been stuck on this for months now and don't know what to do anymore.

from camera import VideoCamera
import cv2
import time

app = Flask(__name__)
app.config['SECRET_KEY'] = 'hello'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///drug1.sqlite'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)

med_list = []
timestr = time.strftime("%Y%m%d-%H%M%S")



cam = cv2.VideoCapture(0)
video_stream = VideoCamera()


@app.route('/')
def index():
    return render_template('vid.html')

def gen(camera):
    while True:
        frame = camera.get_frame()
        yield (b'--frame\r\n'
               b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n\r\n')

@app.route('/video_feed')
def video_feed():


@app.route("/scan", methods =["POST","GET"])
def scan():
    drug_list = []
    if request.method == 'POST':
        bc = request.form['barcode']
        print(bc)

        if request.form['submit_button'] == 'Next':
            return redirect(url_for('scan2',image=image_link,drug=drug,strength=strength,med_list=med_list))
    return render_template("scan.html")


@app.route("/scan2", methods =["POST","GET"])
def scan2():
    drug = session.get("drug",None)
    image_link = session.get("image_link",None)
    strength = session.get("strength",None)
    
    if request.method == 'POST':
        if request.form['submit_button'] == 'Next':

            retval, frame = cam.read()
            cv2.imwrite('img{}.png'.format(timestr), frame)
            
            return redirect(url_for('scan'))
    return render_template("scan2.html",image=image_link,drug=drug,strength=strength)
spg719
  • 49
  • 1
  • 5
  • run it in console/terminal to see if you get error message. – furas Jan 03 '21 at 07:42
  • do you want to capture image and run stream at the same time ? It can make problem - camera can be busy by streaming video and trying to access the same camera again `cv2.VideoCapture(0)` and get frame `cam.read()` can makes problem. Maybe you should use `camera.get_frame()` instead of `cam.read()` – furas Jan 03 '21 at 07:49
  • VideoCapture opens a camera on the **server**. if this is supposed to be a web app, you should get video from the **client**, i.e. browser. you are also opening multiple video streams for no reason: `cam = cv2.VideoCapture(0); video_stream = VideoCamera()` pick one and stick with it. – Christoph Rackwitz Jan 03 '21 at 08:47
  • @Christopher, how do i open the camera on the client side? – spg719 Jan 03 '21 at 12:27
  • @spg719 if you want to access camera on user's computer then you have to use JavaScript because only web browser has access to user's camera. Python which runs on server can't access camera which is on client computer. – furas Jan 03 '21 at 12:45
  • @furas thanks so much. Unfortunately I only know Python, can you guide me to some JavaScript I can run to do what I need to do? – spg719 Jan 03 '21 at 13:31
  • are you sure you want to access user's camera instead of camera used in `video_stream = VideoCamera()` ? – furas Jan 03 '21 at 13:38
  • I guess your question makes me wonder. What I want is the client to be able to take a picture of something, picture will then be saved to server and will all be sent to the client in the end and stored in server sqlite database. What do you recommend I should do? – spg719 Jan 03 '21 at 13:42
  • but do you mean to take a picture using camera built-in user's laptop ? – furas Jan 03 '21 at 13:49

1 Answers1

2

If you want to take a picture using built-in camera in user's laptop then you have to use JavaScript because only web browser has access to user's camera - server can't access user's computer.

Here is example which displays stream from user's camera and it can take screenshot. But it still need JavaScript to send it to server.

from flask import Flask, render_template_string


app = Flask(__name__)


@app.route('/')
def index():
    return render_template_string('''
<video id="video" width="640" height="480" autoplay style="background-color: grey"></video>
<button id="snap">Take Photo</button>
<canvas id="canvas" width="640" height="480" style="background-color: grey"></canvas>

<script>

// Elements for taking the snapshot
var video = document.getElementById('video');
var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');

// Get access to the camera!
if(navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
    // Not adding `{ audio: true }` since we only want video now
    navigator.mediaDevices.getUserMedia({ video: true }).then(function(stream) {
        //video.src = window.URL.createObjectURL(stream);
        video.srcObject = stream; // assing stream to <video>
        video.play();             // play stream
    });
}

// Trigger photo take
document.getElementById("snap").addEventListener("click", function() {
    context.drawImage(video, 0, 0, 640, 480);  // copy video frame to canvas
});

</script>
''')

    
if __name__ == '__main__':    
    app.run(debug=True)

EDIT:

Here is example which can send image on server.

And few useful links which I had with this code

https://developers.google.com/web/fundamentals/media/capturing-images

https://github.com/jhuckaby/webcamjs

https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob

from flask import Flask, render_template_string, request


app = Flask(__name__)


@app.route('/')
def index():
    return render_template_string('''
<video id="video" width="640" height="480" autoplay style="background-color: grey"></video>
<button id="send">Take & Send Photo</button>
<canvas id="canvas" width="640" height="480" style="background-color: grey"></canvas>

<script>

// Elements for taking the snapshot
var video = document.getElementById('video');
var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');

// Get access to the camera!
if(navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
    // Not adding `{ audio: true }` since we only want video now
    navigator.mediaDevices.getUserMedia({ video: true }).then(function(stream) {
        //video.src = window.URL.createObjectURL(stream);
        video.srcObject = stream;
        video.play();
    });
}

// Trigger photo take
document.getElementById("send").addEventListener("click", function() {
    context.drawImage(video, 0, 0, 640, 480); // copy frame from <video>
    canvas.toBlob(upload, "image/jpeg");  // convert to file and execute function `upload`
});

function upload(file) {
    // create form and append file
    var formdata =  new FormData();
    formdata.append("snap", file);
    
    // create AJAX requests POST with file
    var xhr = new XMLHttpRequest();
    xhr.open("POST", "{{ url_for('upload') }}", true);
    xhr.onload = function() {
        if(this.status = 200) {
            console.log(this.response);
        } else {
            console.error(xhr);
        }
        alert(this.response);
    };
    xhr.send(formdata);
}

</script>
''')

@app.route('/upload', methods=['GET', 'POST'])
def upload():
    if request.method == 'POST':
        #fs = request.files['snap'] # it raise error when there is no `snap` in form
        fs = request.files.get('snap')
        if fs:
            print('FileStorage:', fs)
            print('filename:', fs.filename)
            fs.save('image.jpg')
            return 'Got Snap!'
        else:
            return 'You forgot Snap!'
    
    return 'Hello World!'
    
    
if __name__ == '__main__':    
    app.run(debug=True, port=5000)

EDIT:

I found example which sends stream to server, it draws red border and text with current time and it sends it back to browser.

# https://developers.google.com/web/fundamentals/media/capturing-images
# https://github.com/jhuckaby/webcamjs
# https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob
# https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toDataURL
# https://developer.mozilla.org/en-US/docs/Web/API/FileReader/readAsDataURL

from flask import Flask, render_template_string, request, make_response
import cv2
import numpy as np
import datetime

app = Flask(__name__)


@app.route('/')
def index():
    return render_template_string('''
<video id="video" width="320" height="240" autoplay style="background-color: grey"></video>
<button id="send">Take & Send Photo</button>
<canvas id="canvas" width="320" height="240" style="background-color: grey"></canvas>
<img id="image" src="" width="320" height="240" style="background-color: grey"></img>
<img id="image64" src="" width="320" height="240" style="background-color: grey"></img>

<script>

// Elements for taking the snapshot
var video = document.getElementById('video');

// Element to display snapshot

    // you need canvas to get image. canvas can be hidden using `createElement("canvas")`
    // var canvas = document.createElement("canvas");
   
var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');

var image = document.getElementById('image');
var image64 = document.getElementById('image64');

// Get access to the camera!
if(navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
    // Not adding `{ audio: true }` since we only want video now
    navigator.mediaDevices.getUserMedia({ video: true }).then(function(stream) {
        //video.src = window.URL.createObjectURL(stream);
        video.srcObject = stream;
        video.play();
    
        //console.log('setInterval')
        window.setInterval(function() {
            //context.drawImage(video, 0, 0);
            context.drawImage(video, 0, 0, 320, 240); // better use size because camera may gives data in different size then <video> is displaying
            
            image64.src = canvas.toDataURL();  
            canvas.toBlob(upload, "image/jpeg");
        }, 100);    
    });
}

// Trigger photo take
document.getElementById("send").addEventListener("click", function() {
    //context.drawImage(video, 0, 0);
    context.drawImage(video, 0, 0, 320, 240); // better use size because camera may gives data in different size then <video> is displaying
    image64.src = canvas.toDataURL();  

    canvas.toBlob(upload, "image/jpeg");
});

function upload(file) {

    // create form 
    var formdata =  new FormData();
    
    // add file to form
    formdata.append("snap", file);
    
    // create AJAX connection
    var xhr = new XMLHttpRequest();
    xhr.open("POST", "{{ url_for('upload') }}", true);
    xhr.responseType = 'blob';   
    // define function which get response
    xhr.onload = function() {
        
        if(this.status = 200) {
            //console.log(this.response);
        } else {
            console.error(xhr);
        }
        
        //alert(this.response);

        //img.onload = function(){
        //    ctx.drawImage(img, 0, 0)
        //}

        image.src = URL.createObjectURL(this.response); // blob
    };
    
    // send form in AJAX
    xhr.send(formdata);
    
    //image.src = URL.createObjectURL(file);
}

    
</script>
''')

def send_file_data(data, mimetype='image/jpeg', filename='output.jpg'):
    # https://stackoverflow.com/questions/11017466/flask-to-return-image-stored-in-database/11017839
    
    response = make_response(data)
    response.headers.set('Content-Type', mimetype)
    response.headers.set('Content-Disposition', 'attachment', filename=filename)
    
    return response
    
@app.route('/upload', methods=['GET', 'POST'])
def upload():
    if request.method == 'POST':
        #fs = request.files['snap'] # it raise error when there is no `snap` in form
        fs = request.files.get('snap')
        if fs:
            #print('FileStorage:', fs)
            #print('filename:', fs.filename)
            
            # https://stackoverflow.com/questions/27517688/can-an-uploaded-image-be-loaded-directly-by-cv2
            # https://stackoverflow.com/a/11017839/1832058
            img = cv2.imdecode(np.frombuffer(fs.read(), np.uint8), cv2.IMREAD_UNCHANGED)
            #print('Shape:', img.shape)
            # rectangle(image, start_point, end_point, color, thickness)
            img = cv2.rectangle(img, (20, 20), (300, 220), (0, 0, 255), 2)
            
            text = datetime.datetime.now().strftime('%Y.%m.%d %H.%M.%S.%f')
            img = cv2.putText(img, text, (5, 15), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 1, cv2.LINE_AA) 
            #cv2.imshow('image', img)
            #cv2.waitKey(1)
            
            # https://jdhao.github.io/2019/07/06/python_opencv_pil_image_to_bytes/
            ret, buf = cv2.imencode('.jpg', img)
            
            #return f'Got Snap! {img.shape}'
            return send_file_data(buf.tobytes())
        else:
            return 'You forgot Snap!'
    
    return 'Hello World!'
    
    
if __name__ == '__main__':    
    app.run(debug=True, port=5000)

Camera works with HTTP only on address 127.0.0.1

On 0.0.0.0 it needs HTTPS but it needs cert. For test you can use ssl_context='adhoc' but browser will warn you that it is insecure connection and you will have to accept it.

app.run(host='0.0.0.0', port=5000, ssl_context='adhoc')
furas
  • 134,197
  • 12
  • 106
  • 148
  • Thank you, you are VERY helpful! this code worked for me, the only thing I wasn't able to do is return redirect(url_for('.....')) after the image is saved to the server. I tried entering it here under return for @app.route('/upload', methods=['GET', 'POST']) but it returns a pop up dialog with the html contents of the url I'm trying to redirect to – spg719 Jan 03 '21 at 16:28
  • image is send by `JavaScript`, not by browser engine, so `redirect` will not work because your information is send back to JavaScript, not directly to browser engine. You have to send information to `JavaScript` and `JavaScript` should use `document.location = "new url"` to redirect browser. – furas Jan 03 '21 at 16:31
  • Sorry I know its been a while, how do I select the back facing webcam (on an android device)? – spg719 Jul 28 '21 at 19:36
  • I never do this but I found [Select camera on Android Chrome](https://stackoverflow.com/questions/38182555/select-camera-on-android-chrome). It uses `sourceId` in `.getUserMedia({audio: false, video: { sourceId: VideoId } }..., )` to select device. It also shows how to get list of all devices. – furas Jul 29 '21 at 05:31