Python - Keras API 服务器在经过训练的自定义模型上处理图像

Python - Keras API server to process image on trained custom model

我已经按照本教程创建了一个自定义对象 YoloV3 Keras 模型: https://momoky.space/pythonlessons/YOLOv3-object-detection-tutorial/tree/master/YOLOv3-custom-training

模型工作得很好,我的下一个目标是创建一个 Python Flask API 能够在上传后处理图像。 我已经开始修改 Code here for image detection

这是我添加的代码:

@app.route('/api/test', methods=['POST'])
def main():
    img = request.files["image"].read()
    img = Image.open(io.BytesIO(img))
    npimg=np.array(img)
    image=npimg.copy()
    image=cv2.cvtColor(image,cv2.COLOR_BGR2RGB)
    #cv2.imshow("Image", image)
    #cv2.waitKey()
    cv2.imwrite('c:\yolo\temp.jpg', image)
    image = 'c:\yolo\temp.jpg'
    yolo = YOLO()
    r_image, ObjectsList = yolo.detect_img(image)
    #response = {ObjectsList}
    response_pikled = jsonpickle.encode(ObjectsList)
    #yolo.close_session()
    return Response(response=response_pikled, status=200, mimetype="application/json")
app.run(host="localhost", port=5000)

所以我的问题是它只适用于第一次迭代,当我上传新图片时,我收到以下错误:

File "C:\Users\xxx\Anaconda3\envs\yolo\lib\site-packages\tensorflow\python\client\session.py", line 929, in run
    run_metadata_ptr)
  File "C:\Users\xxx\Anaconda3\envs\yolo\lib\site-packages\tensorflow\python\client\session.py", line 1095, in _run
    'Cannot interpret feed_dict key as Tensor: ' + e.args[0])
TypeError: Cannot interpret feed_dict key as Tensor: Tensor Tensor("Placeholder:0", shape=(3, 3, 3, 32), dtype=float32) is not an element of this graph.

这是代码的原始静态部分:

if __name__=="__main__":
    yolo = YOLO()
    image = 'test.png'
    r_image, ObjectsList = yolo.detect_img(image)
    print(ObjectsList)
    #cv2.imshow(image, r_image)
    cv2.imwrite('detect.png', r_image)

    yolo.close_session()

真正让我困惑的是如何在应用程序启动时加载模型,并在每次发布新图像时执行检测。 谢谢

更新

在构造器部分有一个引用的 Keras 后端会话:

 **def __init__(self, **kwargs):
        self.__dict__.update(self._defaults) # set up default values
        self.__dict__.update(kwargs) # and update with user overrides
        self.class_names = self._get_class()
        self.anchors = self._get_anchors()
        self.sess = K.get_session()
        self.boxes, self.scores, self.classes = self.generate()**

添加 K.clear_session 后,它适用于多个系列请求:

 @app.route('/api/test', methods=['POST'])
    def main():
        img = request.files["image"].read()
        img = Image.open(io.BytesIO(img))
        npimg=np.array(img)
        image=npimg.copy()
        image=cv2.cvtColor(image,cv2.COLOR_BGR2RGB)
        #cv2.imshow("Image", image)
        #cv2.waitKey()
        cv2.imwrite('c:\yolo\temp.jpg', image)
        image = 'c:\yolo\temp.jpg'
        yolo = YOLO()
        r_image, ObjectsList = yolo.detect_img(image)
        #response = {ObjectsList}
        response_pikled = jsonpickle.encode(ObjectsList)
        #yolo.close_session()
        K.clear_session()
        return Response(response=response_pikled, status=200, mimetype="application/json")

将有可能避免模型、锚点和 类 需要在每次计算时加载以避免这种情况:

ogs/000/trained_weights_final.h5 model, anchors, and classes loaded.
127.0.0.1 - - [27/Dec/2019 22:58:49] "?[37mPOST /api/test HTTP/1.1?[0m" 200 -
logs/000/trained_weights_final.h5 model, anchors, and classes loaded.
127.0.0.1 - - [27/Dec/2019 22:59:08] "?[37mPOST /api/test HTTP/1.1?[0m" 200 -
logs/000/trained_weights_final.h5 model, anchors, and classes loaded.
127.0.0.1 - - [27/Dec/2019 22:59:33] "?[37mPOST /api/test HTTP/1.1?[0m" 200 

-

在 YOLO 构造函数中尝试添加:

from keras import backend as K
K.clear_session()

我已经设法把它弄好并 运行宁作为原型。我已经上传了一个 repo:vulcan25/image_processor 实现了这一切。

我调查的第一件事是教程中该代码中方法 YOLO.detect_img 的功能。此方法接受一个文件名,它立即由原始代码中的 cv2.imread 处理:#L152-L153。然后返回的数据由 self.detect_image(注意拼写差异)在内部处理,结果显示为 cv2.show

这种行为对 Web 应用程序不利,我想将所有内容都保存在内存中,因此认为更改该功能的最佳方法是子类化 YOLO 并覆盖 detect_img 方法,使其表现不同。所以在 processor/my_yolo.py 我做了类似的事情:

from image_detect import YOLO as stock_yolo

class custom_yolo(stock_yolo):
    def detect_img(self, input_stream):

        image = cv2.imdecode(numpy.fromstring(input_stream, numpy.uint8), 1)

        original_image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
        original_image_color = cv2.cvtColor(original_image, cv2.COLOR_BGR2RGB)

        r_image, ObjectsList = self.detect_image(original_image_color)
        is_success, output_stream  = cv2.imencode(".jpg", r_image)

        return is_success, output_stream

Note: In a later decision, I pulled image_detect.py into my repo to append K.clear_session(). It would have been possible to put the above mod in that file also, but I've stuck with subclassing for that part.

它接受一个流,然后使用 cv2.imencode and cv2.imdecode 分别代替 imshowimread

我们现在可以定义一个单独的函数,它反过来 运行 所有图像处理的东西。这将那部分代码(和依赖项)与你的 Flask 应用程序分开,这很好:


yolo = custom_yolo() # create an object from the custom class defined above.

def process(intput_stream):

     start_time = time.time()

     is_success, output_stream = yolo.detect_img(input_stream)
     io_buf = io.BytesIO(output_stream)

     print("--- %s seconds ---" % (time.time() - start_time))

     return is_success, io_buf.read()

在 Flask 中,我们可以用相同的方式调用它,我们已经有可用的上传文件流:request.files['file'].read()(这实际上是 werkzeug.datastructures.FileStorage 对象的一个​​方法, 正如我 ).

作为旁注,此功能可能来自终端 运行。如果你要启动我的 repo,你需要在 processor 容器中执行此操作(请参阅我的自述文件末尾的 docker exec 语法)...

from my_yolo import process
with f.open('image.jpg', 'rb') as f:
    is_sucess, processed_data = process(f.read())

然后将结果写入文件:

with f.open('processed_image.jpg', 'wb' as f):    
    f.write(processed_data)

请注意,我的存储库实际上有两个独立的 Flask 应用程序(基于我放在一起的另一个 upload script,它在前端实现了 dropzone.js)。

我可以运行两种模式:

  1. processor/src/app.py:可在端口 5001 上访问,此 运行s process 直接(传入请求阻塞,直到处理的数据返回)。
  2. flask/src/app.py:可在端口 5000 上访问,这会为每个传入请求创建一个 rq 作业,processor 容器然后 运行 作为工作程序来处理这些请求来自队列的请求。

每个应用程序都有自己的 index.html,它在前端执行自己独特的实现。模式 (1) 将图像直接写入页面,模式 (2) 将 link 添加到页面,单击该页面会导致一个单独的端点为图像提供服务(处理时)。

主要区别在于 process 的调用方式。使用模式 (1) processor/src/app.py:

from my_yolo import process

if file and allowed_file(file.filename):
    # process the upload immediately
    input_data = file.read()
    complete, data = process(input_data)

如评论中所述,我发现使用此模式的转换非常快:~1.6s per image on CPU。此脚本还使用一个 redis 集来维护上传文件的列表,可用于进一步向下 view 端点进行验证。

模式 (2) flask/src/app.py:

from qu import image_enqueue

if file and allowed_file(file.filename):
            input_data = file.read()

            job = img_enqueue(input_data)
            return jsonify({'url': url_for('view', job_id=job.id)})

我已经实现了一个单独的文件 flask/src/qu.py,它实现了这个 img_enqueue 函数,它最终从 flask/src/my_yolo.py 加载了 process 函数,它被定义为:

def process(data): pass

这是一个重要的归宿。通常使用 rq 时,此函数的内容将在与 flask 服务相同的代码库中定义。事实上,我实际上已经将业务逻辑放在 processor/src/my_yolo.py 中,这允许我们分离具有图像处理依赖项的容器,并最终将其托管在其他地方,只要它与以下内容共享 redis 连接flask 服务。

请查看存储库中的代码以获取更多信息,并随时记录针对该存储库的问题以进行任何进一步的查询(或者如果您遇到困难)。请注意,我可能会引入重大更改,因此您可能希望分叉。

我尽量保持简单。从理论上讲,这可以稍微编辑以支持不同的 processor/Dockerfile 来处理任何处理工作负载,但相同的前端允许您从流中提交任何类型的数据:图像、CSV、其他文本等。

Things that really confuse me is how to load the model when the application start, and execute detection every time a new image is posted. Thank you

当你 运行 这种模式 (1) 时你会注意到它是完美的,因为依赖项在 flask 服务器启动时加载 (~17.s) 并且单个图像处理需要 ~1s.这是理想的,尽管可能会导致服务器上的整体内存使用率更高,因为每个 WSGI worker 都需要加载所有依赖项。

当 运行 处于模式 (2) 时 - 将处理传递给 rq worker,每次处理图像时都会加载库,因此速度要慢得多。我会尝试解决这个问题,我只需要研究如何在 rq 部署中预加载库;我之前接近这个,但那是我偶然发现 K.clear_session() 问题的时候,所以还没有时间重新测试这个修复程序(还)。