Creating a function using yield instead of return

2019-04-12 04:11发布

问题:

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)

回答1:

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.