4

I would like to create a function that reads frames from an HTTP stream using requests and returns each frame. But because of the fact that the stream reader is based on an iterator object (if I understand correctly), returning a frame is breaking the stream.

The code I am using (works perfectly fine, from this answer):

import cv2
import requests
import numpy as np

r = requests.get('http://roofcam.warwick.ac.uk/cgi-bin/faststream.jpg', stream=True)
if(r.status_code == 200):
    bytes_buffer = bytes()
    for chunk in r.iter_content(chunk_size=1024):
        bytes += chunk
        a = bytes_buffer.find(b'\xff\xd8')
        b = bytes_buffer.find(b'\xff\xd9')
        if a != -1 and b != -1:
            jpg = bytes_buffer[a:b+2]
            bytes_buffer = bytes_buffer[b+2:]
            i = cv2.imdecode(np.fromstring(jpg, dtype=np.uint8), cv2.IMREAD_COLOR)
            cv2.imshow('i', i)
            if cv2.waitKey(1) == 27:
                exit(0)
else:
    print("Received unexpected status code {}".format(r.status_code))

Concept of what I'd like to do (with the return that I'd like to work if it was a while function instead of a for using iterator):

import cv2
import requests
import numpy as np

r = requests.get('http://roofcam.warwick.ac.uk/cgi-bin/faststream.jpg', stream=True)

def get_frame_from_stream(r):
    if(r.status_code == 200):
        bytes_buffer = bytes()
        for chunk in r.iter_content(chunk_size=1024):
            bytes_buffer += chunk
            a = bytes_buffer.find(b'\xff\xd8')
            b = bytes_buffer.find(b'\xff\xd9')
            if a != -1 and b != -1:
                jpg = bytes_buffer[a:b + 2]
                bytes_buffer = bytes_buffer[b + 2:]
                i = cv2.imdecode(np.fromstring(
                    jpg, dtype=np.uint8), cv2.IMREAD_COLOR)
                return i

    else:
        print("Received unexpected status code {}".format(r.status_code))
        return None

while True:
    if img is not None:
        img = get_frame_from_stream(r)
        cv2.imshow('i', img)
        cv2.waitKey(0)
    else:
        break

So basically I'd like to return each frame where the original code is displaying the frame so that I can perform some processing on it. But I don't understand how exactly is the iter_content allowing the original code to work continuously.

(I didn't know how to name the question - will welcome a better title)

Michał Gacka
  • 2,935
  • 2
  • 29
  • 45
  • iter_content is a generator class I believe. – RandomHash May 24 '17 at 14:54
  • @0x52-0x75-0x63-0x79 thanks for your input. I did suspect that but I'm not sure if I can make it work like I described, and how to do that. I am trying to read about yield and generators but theory is one thing and I have a hard time trying to apply it in this case. – Michał Gacka May 24 '17 at 16:20

1 Answers1

4

Python has a very beautiful concept of generators, which may be the thing you are looking for, so basically to fabricate a generator we use keyword yield instead of return. The basic difference between these two keywords is that the return statement simply halts the function execution once encountered, while on the other hand yield keyword let's the execution to continue and keeps on generating value until alive. It can be visualized in a simple example as:

def sample_function():
    # The body would be replaced by image generating code.
    for i in xrange(20):
        yield i**2

for x in sample_function():
    print x, 
>>> 0 1 4 9 16 25 36 49 64 81 100 121 144 169 196 225 256 289 324 361

If We would had reproduce the same scenario using return statement then it may look like:

def sample_function(i):
    return i**2

for i in xrange(0, 20):
    print sample_function(i),

So in the above code if you replace the return keyword with yield, then you can iterate the frames as:

for frame in get_frame_from_stream(r):
    cv2.imshow('i', frame)

There is no need of a while loop is this case, unless and until this stream is alive, the method would keep on generating frames.

ZdaR
  • 22,343
  • 7
  • 66
  • 87
  • Works like a charm. I did try doing it a little bit like that - using yield instead of return. But the 'for x in sample_function()' bit was a mystery to me and I tried using it like a normal function instead of in a 'for' statement. Thank you. – Michał Gacka May 30 '17 at 07:29