opencv 使用FastAPI将视频流传输到网络浏览器

im9ewurl  于 2023-01-02  发布在  其他
关注(0)|答案(3)|浏览(368)

我在FLASK中找到了一个例子,在Web浏览器中通过RTSP协议传输摄像机。
我试着在fastapi中使用同样的例子,但是我没有得到它,图像被冻结了。
flask 中的示例(正常工作):

def generate():
    # grab global references to the output frame and lock variables
    global outputFrame, lock
    # loop over frames from the output stream
    while True:
        # wait until the lock is acquired
        with lock:
            # check if the output frame is available, otherwise skip
            # the iteration of the loop
            if outputFrame is None:
                continue
            # encode the frame in JPEG format
            (flag, encodedImage) = cv2.imencode(".jpg", outputFrame)
            # ensure the frame was successfully encoded
            if not flag:
                continue
        # yield the output frame in the byte format
        yield (b'--frame\r\n' b'Content-Type: image/jpeg\r\n\r\n' +
               bytearray(encodedImage) + b'\r\n')

@app.route("/")
def video_feed():
    # return the response generated along with the specific media
    # type (mime type)
    return Response(generate(),
                    mimetype="multipart/x-mixed-replace; boundary=frame")

我试着在FastAPI中这样做,但是只有第一帧出现,被冻结了。

def generate():
    # grab global references to the output frame and lock variables
    global outputFrame, lock
    # loop over frames from the output stream
    while True:
        # wait until the lock is acquired
        with lock:
            # check if the output frame is available, otherwise skip
            # the iteration of the loop
            if outputFrame is None:
                continue
            # encode the frame in JPEG format
            (flag, encodedImage) = cv2.imencode(".jpg", outputFrame)
            # ensure the frame was successfully encoded
            if not flag:
                continue
        # yield the output frame in the byte format
        yield b''+bytearray(encodedImage)

@app.get("/")
def video_feed():
    # return the response generated along with the specific media
    # type (mime type)
    # return StreamingResponse(generate())
    return StreamingResponse(generate(), media_type="image/jpeg")

有人知道如何在fastapi中使用相同的函数吗?
如果有人对完整的代码感兴趣,我举了下面的例子:https://www.pyimagesearch.com/2019/09/02/opencv-stream-video-to-web-browser-html-page/

7kqas0il

7kqas0il1#

在这里张贴后,我想出了如何修复它。
video_feed函数中,在media_type参数中,只是按照与 flask 中相同的方式放置:

@app.get("/")
def video_feed():
    # return the response generated along with the specific media
    # type (mime type)
    # return StreamingResponse(generate())
    return StreamingResponse(generate(), media_type="multipart/x-mixed-replace;boundary=frame")

在函数中生成:

yield (b'--frame\r\n' b'Content-Type: image/jpeg\r\n\r\n' +
               bytearray(encodedImage) + b'\r\n')

我的完整代码:
http://github.com/mpimentel04/rtsp_fastapi

bzzcjhmw

bzzcjhmw2#

范围请求(适用于视频/PDF等...)

import os
from typing import BinaryIO

from fastapi import HTTPException, Request, status
from fastapi.responses import StreamingResponse

def send_bytes_range_requests(
    file_obj: BinaryIO, start: int, end: int, chunk_size: int = 10_000
):
    """Send a file in chunks using Range Requests specification RFC7233

    `start` and `end` parameters are inclusive due to specification
    """
    with file_obj as f:
        f.seek(start)
        while (pos := f.tell()) <= end:
            read_size = min(chunk_size, end + 1 - pos)
            yield f.read(read_size)

def _get_range_header(range_header: str, file_size: int) -> tuple[int, int]:
    def _invalid_range():
        return HTTPException(
            status.HTTP_416_REQUESTED_RANGE_NOT_SATISFIABLE,
            detail=f"Invalid request range (Range:{range_header!r})",
        )

    try:
        h = range_header.replace("bytes=", "").split("-")
        start = int(h[0]) if h[0] != "" else 0
        end = int(h[1]) if h[1] != "" else file_size - 1
    except ValueError:
        raise _invalid_range()

    if start > end or start < 0 or end > file_size - 1:
        raise _invalid_range()
    return start, end

def range_requests_response(
    request: Request, file_path: str, content_type: str
):
    """Returns StreamingResponse using Range Requests of a given file"""

    file_size = os.stat(file_path).st_size
    range_header = request.headers.get("range")

    headers = {
        "content-type": content_type,
        "accept-ranges": "bytes",
        "content-encoding": "identity",
        "content-length": str(file_size),
        "access-control-expose-headers": (
            "content-type, accept-ranges, content-length, "
            "content-range, content-encoding"
        ),
    }
    start = 0
    end = file_size - 1
    status_code = status.HTTP_200_OK

    if range_header is not None:
        start, end = _get_range_header(range_header, file_size)
        size = end - start + 1
        headers["content-length"] = str(size)
        headers["content-range"] = f"bytes {start}-{end}/{file_size}"
        status_code = status.HTTP_206_PARTIAL_CONTENT

    return StreamingResponse(
        send_bytes_range_requests(open(file_path, mode="rb"), start, end),
        headers=headers,
        status_code=status_code,
    )

用法

from fastapi import FastAPI

app = FastAPI()

@app.get("/video")
def get_video(request: Request):
    return range_requests_response(
        request, file_path="path_to_my_video.mp4", content_type="video/mp4"
    )
yebdmbv4

yebdmbv43#

答案很简单:

def get_video_range_response(request: Request, file_path: str, content_type: str = "video/mp4")
    file_size = os.stat(file_path).st_size
    h = request.headers.get("range").replace("bytes=", "").split("-")
    start = int(h[0]) if h[0] != "" else 0

    maxSize = 200000
    end = start + maxSize  # this is the expected end
    if end >= file_size #if end > file_size then obviously end = file_size - 1
        end = file_size - 1
    size = end - start
    headers = {"content-type": content_type,
               "accept-ranges": "bytes",
               "content-encoding": "identity",
               "content-length": str(size),
               "content-range": f" bytes {start}-{end}/{file_size}",
               }

    file_obj = open(file_path, mode="rb")
    file_obj.seek(start)
    data = file_obj.read(size)
    file_obj.close()
    status_code = status.HTTP_206_PARTIAL_CONTENT

    return Response(content=data,
                    status_code=status_code,
                    headers=headers,
                    media_type=content_type
                    )

用途

@app.get('/Video')
def video_endpoint(req: Request):
    video_path = r"C:\WOI\pict\videos\Modi.mp4"
    return get_video_range_response(req, file_path = video_path, content_type = "video/mp4")

超文本标记语言

<video controls class="w-100">
    <source src="/Video" type="video/mp4">        
</video>

相关问题