用 python+grpc+yolo 進行目標檢測

我們模型開發(fā)完成后往往需要基于一些web服務模塊將模型部署成可被外部訪問的服務形式,用的最多的就是flask框架了,可以很方便地將模型暴露成web服務接口,現(xiàn)在有一個新的需求就是需要使用grpc方式來開發(fā)接口,用于集群服務內部之間的相互訪問調用。
gRPC有什么好處以及在什么場景下需要用gRPC既然是server/client模型,那么我們直接用restful api不是也可以滿足嗎,為什么還需要RPC呢?下面我們就來看看RPC到底有哪些優(yōu)勢gRPC vs. Restful APIgRPC和restful API都提供了一套通信機制,用于server/client模型通信,而且它們都使用http作為底層的傳輸協(xié)議(嚴格地說, gRPC使用的http2.0,而restful api則不一定)。不過gRPC還是有些特有的優(yōu)勢,如下:1、gRPC可以通過protobuf來定義接口,從而可以有更加嚴格的接口約束條件。關于protobuf可以參見筆者之前的小文Google Protobuf簡明教程2、另外,通過protobuf可以將數(shù)據(jù)序列化為二進制編碼,這會大幅減少需要傳輸?shù)臄?shù)據(jù)量,從而大幅提高性能。3、gRPC可以方便地支持流式通信(理論上通過http2.0就可以使用streaming模式, 但是通常web服務的restful api似乎很少這么用,通常的流式數(shù)據(jù)應用如視頻流,一般都會使用專門的協(xié)議如HLS,RTMP等,這些就不是我們通常web服務了,而是有專門的服務器應用。)
在此之前我聽過rpc(Remote Procedure Call),即:遠程過程調用,但是對于grpc還是知之甚少,所以這里還是需要花點時間來學習了解一下的,這里就以一個實際的目標檢測類的應用來進行實踐學習。
grpc的官網介紹截圖如下:

一個基礎的grpc應用問題主要包含四個部分:
1、編寫 .proto文件
RPC 是兩個子系統(tǒng)之間進行的直接消息交互,它使用操作系統(tǒng)提供的套接字來作為消息的載體,以特定的消息格式來定義消息內容和邊界。gRPC 是 Google 開源的基于 Protobuf 和 Http2.0 協(xié)議的通信框架,Google 深度學習框架 tensorflow 底層的 RPC 通信就完全依賴于 gRPC 庫。gRPC通過protobuf來定義接口和數(shù)據(jù)類型。
這里我們編寫的 data.proto 內容如下:
syntax = "proto3";//package example;service FormatData { //定義服務,用在rpc傳輸中rpc DoFormat(actionrequest) returns (actionresponse){}}message actionrequest {string text = 1;}message actionresponse{string text=1;}
2、運行命令生成 data_pb2_grpc.py 和data_pb2.py
python -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. ./data.proto上述命令執(zhí)行結束后會生成上述兩個腳本文件,內容如下:
data_pb2.py
# -*- coding: utf-8 -*-# Generated by the protocol buffer compiler. DO NOT EDIT!# source: data.proto"""Generated protocol buffer code."""from google.protobuf import descriptor as _descriptorfrom google.protobuf import message as _messagefrom google.protobuf import reflection as _reflectionfrom google.protobuf import symbol_database as _symbol_database# @@protoc_insertion_point(imports)_sym_db = _symbol_database.Default()DESCRIPTOR = _descriptor.FileDescriptor(name='data.proto',package='',syntax='proto3',serialized_options=None,create_key=_descriptor._internal_create_key,serialized_pb='......')_ACTIONREQUEST = _descriptor.Descriptor(name='actionrequest',full_name='actionrequest',filename=None,file=DESCRIPTOR,containing_type=None,create_key=_descriptor._internal_create_key,fields=[_descriptor.FieldDescriptor(name='text', full_name='actionrequest.text', index=0,number=1, type=9, cpp_type=9, label=1,has_default_value=False, default_value=b"".decode('utf-8'),message_type=None, enum_type=None, containing_type=None,is_extension=False, extension_scope=None,serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),],extensions=[],nested_types=[],enum_types=[],serialized_options=None,is_extendable=False,syntax='proto3',extension_ranges=[],oneofs=[],serialized_start=14,serialized_end=43,)_ACTIONRESPONSE = _descriptor.Descriptor(name='actionresponse',full_name='actionresponse',filename=None,file=DESCRIPTOR,containing_type=None,create_key=_descriptor._internal_create_key,fields=[_descriptor.FieldDescriptor(name='text', full_name='actionresponse.text', index=0,number=1, type=9, cpp_type=9, label=1,has_default_value=False, default_value=b"".decode('utf-8'),message_type=None, enum_type=None, containing_type=None,is_extension=False, extension_scope=None,serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),],extensions=[],nested_types=[],enum_types=[],serialized_options=None,is_extendable=False,syntax='proto3',extension_ranges=[],oneofs=[],serialized_start=45,serialized_end=75,)DESCRIPTOR.message_types_by_name['actionrequest'] = _ACTIONREQUESTDESCRIPTOR.message_types_by_name['actionresponse'] = _ACTIONRESPONSE_sym_db.RegisterFileDescriptor(DESCRIPTOR)actionrequest = _reflection.GeneratedProtocolMessageType('actionrequest', (_message.Message,), {'DESCRIPTOR' : _ACTIONREQUEST,'__module__' : 'data_pb2'# @@protoc_insertion_point(class_scope:actionrequest)})_sym_db.RegisterMessage(actionrequest)actionresponse = _reflection.GeneratedProtocolMessageType('actionresponse', (_message.Message,), {'DESCRIPTOR' : _ACTIONRESPONSE,'__module__' : 'data_pb2'# @@protoc_insertion_point(class_scope:actionresponse)})_sym_db.RegisterMessage(actionresponse)_FORMATDATA = _descriptor.ServiceDescriptor(name='FormatData',full_name='FormatData',file=DESCRIPTOR,index=0,serialized_options=None,create_key=_descriptor._internal_create_key,serialized_start=77,serialized_end=136,methods=[_descriptor.MethodDescriptor(name='DoFormat',full_name='FormatData.DoFormat',index=0,containing_service=None,input_type=_ACTIONREQUEST,output_type=_ACTIONRESPONSE,serialized_options=None,create_key=_descriptor._internal_create_key,),])_sym_db.RegisterServiceDescriptor(_FORMATDATA)DESCRIPTOR.services_by_name['FormatData'] = _FORMATDATA# @@protoc_insertion_point(module_scope)
data_pb2_grpc.py
# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!"""Client and server classes corresponding to protobuf-defined services."""import grpcimport data_pb2 as data__pb2class FormatDataStub(object):"""定義服務,用在rpc傳輸中"""def __init__(self, channel):"""Constructor.Args:channel: A grpc.Channel."""self.DoFormat = channel.unary_unary('/FormatData/DoFormat',request_serializer=data__pb2.actionrequest.SerializeToString,response_deserializer=data__pb2.actionresponse.FromString,)class FormatDataServicer(object):"""定義服務,用在rpc傳輸中"""def DoFormat(self, request, context):"""Missing associated documentation comment in .proto file."""context.set_code(grpc.StatusCode.UNIMPLEMENTED)context.set_details('Method not implemented!')raise NotImplementedError('Method not implemented!')def add_FormatDataServicer_to_server(servicer, server):rpc_method_handlers = {'DoFormat': grpc.unary_unary_rpc_method_handler(servicer.DoFormat,request_deserializer=data__pb2.actionrequest.FromString,response_serializer=data__pb2.actionresponse.SerializeToString,),}generic_handler = grpc.method_handlers_generic_handler('FormatData', rpc_method_handlers)server.add_generic_rpc_handlers((generic_handler,))# This class is part of an EXPERIMENTAL API.class FormatData(object):"""定義服務,用在rpc傳輸中"""@staticmethoddef DoFormat(request,target,options=(),channel_credentials=None,call_credentials=None,insecure=False,compression=None,wait_for_ready=None,timeout=None,metadata=None):return grpc.experimental.unary_unary(request, target, '/FormatData/DoFormat',data__pb2.actionrequest.SerializeToString,data__pb2.actionresponse.FromString,options, channel_credentials,insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
至此,第二部分的工作就結束了。
3、編寫sever端代碼, server.py
這里主要是編寫服務端的處理代碼。
#!usr/bin/env python#encoding:utf-8from __future__ import division'''__Author__:沂水寒城功能: grpc 服務端'''import ioimport osimport cv2import grpcimport timeimport jsonimport base64import numpy as npfrom PIL import Imagefrom concurrent import futuresimport data_pb2, data_pb2_grpcfrom detect import *_ONE_DAY_IN_SECONDS = 60 * 60 * 24_HOST = 'localhost'_PORT = '8080'class FormatData(data_pb2_grpc.FormatDataServicer):'''重寫接口函數(shù)'''def DoFormat(self, request, context):content = request.textdecode_img = base64.b64decode(content)image = io.BytesIO(decode_img)img = Image.open(image)detect_res=detectImg('name',img)detect_res=str(detect_res)return data_pb2.actionresponse(text=detect_res)def serve():'''服務端處理計算'''grpcServer = grpc.server(futures.ThreadPoolExecutor(max_workers=4))data_pb2_grpc.add_FormatDataServicer_to_server(FormatData(), grpcServer)grpcServer.add_insecure_port(_HOST + ':' + _PORT)grpcServer.start()try:while True:print('=================================start=================================')time.sleep(_ONE_DAY_IN_SECONDS)except KeyboardInterrupt:grpcServer.stop(0)if __name__ == '__main__':serve()
4、編寫客戶端代碼,client.py
這里主要是實現(xiàn)客戶端的請求代碼:
#!usr/bin/env python#encoding:utf-8from __future__ import divisionimport osimport cv2import jsonimport timeimport grpcimport base64import numpy as npfrom PIL import Imageimport data_pb2, data_pb2_grpc_HOST = 'localhost'_PORT = '8080'def run():connection = grpc.insecure_channel(_HOST + ':' + _PORT)print('connection: ', connection)client = data_pb2_grpc.FormatDataStub(channel=connection)print('client: ', client)string = base64.b64encode(img)response = client.DoFormat(data_pb2.actionrequest(text=string))print("response: " + response.text)if __name__ == '__main__':run()
到這里,一個基礎的grpc應用已經開發(fā)完成,可以進行實際的測試使用了,首先在終端啟動服務端,之后執(zhí)行客戶端的請求操作,查看結果輸出。
服務端啟動后輸出如下:

客戶端啟動后輸出如下:

我們這里是借助于yolov3實現(xiàn)的目標檢測服務,原圖如下:

檢測結果如下:

可以看到已經正常計算出來的結果。
作者:沂水寒城,CSDN博客專家,個人研究方向:機器學習、深度學習、NLP、CV
Blog: http://yishuihancheng.blog.csdn.net
贊 賞 作 者





點擊下方閱讀原文加入社區(qū)會員
