OpenVINO Model Server的服務化部署——step2(天空分割模型)


基於OpenVINO的“semantic-segmentation-adas”模型,能夠較為精確的分割出天空;使用OpenCV的seamlessClone等函數,實現天空的無縫替換;基於Django實現網絡化部署。三者結合,實現並部署“天空替換”模型。
目前服務已經上線:打開地址:http://81.68.242.86:8000/upload 就可以體驗,手機端和PC端都可以。雖然界面比較簡陋,速度也比較慢,但是基本可用。總的來說,openvino自帶的這個模型本來是用於道路分割的,不是專用的,能夠出一定效果,但是有些時候不精確;再加上后期處理,還有粗糙的地方。但本文最為重要的是證明工具鏈的可行,探索一條道路,這個是有價值的。
OpenVINO Model Server的服務化部署——step1(OpenVINO™ Model Server Quickstart)
https://www.cnblogs.com/jsxyhelu/p/13796161.html
OpenVINO Model Server的服務化部署——step2(天空分割模型)
https://www.cnblogs.com/jsxyhelu/p/13829051.html
OpenVINO Model Server的服務化部署——step3(django服務構建)
https://www.cnblogs.com/jsxyhelu/p/13878335.html
OpenVINO Model Server的服務化部署——step4(實現天空替換)
https://www.cnblogs.com/jsxyhelu/p/13894565.html
==========================================================================

本系列中關於OpenVINO Model Server的服務化研究,就是為了能夠尋找到一種可行的天空分割的方法。由於使用傳統方法,已經無法很好地解決這個復雜問題,所以轉而研究AI的方法。而服務化部署就是為了最終能夠被更方便地調用這里的AI技術。

一、基於OpenVINO的“天空分割”回顧
semantic-segmentation-adas-0001模型中包含了天空對象。
只接受batch=1的輸入,而它的輸出直接是標注label,需要縮放成為原圖大小,可能還需要進行一些輪廓處理-但是已經基本上實現了“端到端”的效果。下面的圖像中,藍色區域是天空區域。這里需要注意的是,接口文件(***.py)是需要自己來寫的。
 
The net outputs a blob with the shape [B, H=1024, W=2048]. It can be treated as a one-channel feature map, where each pixel is a label of one of the classes.
 
原圖 效果
從上面幾張圖的效果可以看出,雖然有一定的誤差,但是還是在可以接受范圍內的。只要我們在融合上面稍微下一點功夫,這些是看不出來的。
經過進一步研究,能夠得到以下的“天空替換”結果
二、OpenVINO Model Server服務化要點
最容易出錯的地方是 模型文件的准備 ,目前已經驗證可行的方法是在本機按照制定的結構安排文件,而后調用“:ro"參數,將文件結構全部復制到docker中。比如:

我們下載了bin+xml,需要 按照以下模式存放

tree models /
models /
├── model1
│   ├──  1
│   │   ├── ir_model.bin
│   │   └── ir_model.xml
│   └──  2
│       ├── ir_model.bin
│       └── ir_model.xml
└── model2
    └──  1
        ├── ir_model.bin
        ├── ir_model.xml
        └── mapping_config.json

這里的models以及下面的級聯文件夾,都是在本機創建好的。

 
而后調用類似下面的命令行,啟動Docker
docker run -d  -/models : /models :ro  -9000 : 9000 openvino /model_server :latest  --model_path  /models /model1  --model_name face -detection  --port  9000  --log_level DEBUG  --shape  auto
 
參數解釋
docker run 就是啟動docker
-v 表示的是本機和docker中目錄的對應方式, :ro表示是嵌套復制,也就是前面那么多級聯的目錄”原模原樣“的復制過去。本機的文件放在哪里,我們當然知道;docker中的文件放在哪里,其實並不重要。重要的是將這里的文件地址告訴openvino,所以這里的目錄地址和后面的 --model_path是一致的
-p 本機和docker的端口鏡像關系
openvino /model_server :latest 啟動的docker鏡像
--model_path  和前面的 -v要保持一致
--model_name openvino調用的model的名稱
-d 它的意思就是后台運行,你可以去掉來看調試
其它幾個不是太重要, 也不容易寫錯。
 
啟動成功以后,可以運行
docker ps
來看是否運行成功。
 
當然你也可以在docker run中去掉 -d 而基於命令行的方法查看,這里還有其他一些相關命令。
 
sudo docker  ps
sudo docker exec  -it  775c7c9ee1e1  /bin /bash
 
 
三、基於OpenVINO的道路分割服務化部署
3.1 新建model2,將最新的模型下載下來
wget https : / /download. 01.org /opencv / 2021 /openvinotoolkit / 2021. 1 /open_model_zoo /models_bin / 2 /semantic -segmentation -adas - 0001 /FP32 /semantic -segmentation -adas - 0001.bin
wget https : / /download. 01.org /opencv / 2021 /openvinotoolkit / 2021. 1 /open_model_zoo /models_bin / 2 /semantic -segmentation -adas - 0001 /FP32 /semantic -segmentation -adas - 0001.xml

[root@VM - 0 - 13 -centos 1] # cd /models
[root@VM - 0 - 13 -centos models] # tree
.
├── model1
│   └── 1
│       ├── face -detection -retail - 0004.bin
│       └── face -detection -retail - 0004.xml
└── model2
    └── 1
        ├── semantic -segmentation -adas - 0001.bin
        └── semantic -segmentation -adas - 0001.xml

4 directories, 4 files
 
在image目錄中,采用ftp或者wget的方式放置一張圖片
這張圖片由於曝光等問題,天空是比較難以分割出來的,我們看模型效果。
 
3.2修改幾個參數,將服務跑起來,並確認服務正確運行
[root@VM - 0 - 13 -centos models] # docker run -d -v /models:/models:ro -p 9000:9000 openvino/model_server:latest --model_path /models/ model2 --model_name semantic-segmentation-adas --port 9000 --log_level DEBUG --shape auto
27907ca99807fb58184daee3439d821b554199ead70964e6e6bcf233c7ee20f0
[root@VM - 0 - 13 -centos models] # docker ps 
CONTAINER ID        IMAGE                          COMMAND                  CREATED             STATUS              PORTS                    NAMES
27907ca99807        openvino /model_server :latest   "/ovms/bin/ovms --mo…"   5 seconds ago       Up 3 seconds         0. 0. 0. 0 : 9000 - > 9000 /tcp   flamboyant_mahavira
以上兩個步驟,都是和之前操作類似的,是比較簡單的。
 
3.3接口文件的改寫:
服務運行了,但是必須有能夠調用這個服務的接口文件。這個方面的參考比較復雜。我經過一段時間的研究能夠成功調用。這里將幾個需要改寫的地方和願意進行梳理。我們最終形成的接口文件取名sky_detection.py。
3.3.1 圖像尺寸
由於 semantic - segmentation - adas 模型在訓練和推斷的時候,是需要縮放到指定的大小的(具體的大小可以通過查文檔獲得)
Performance

Inputs

The blob with BGR image in format : [B, C = 3, H = 1024, W = 2048], where :

B - batch size,
C - number of channels
H - image height
W - image width
……
故首先需要在參數設置中,設定正確的高度和寬度。比如 客戶端采用
 
python3 sky_detection.py  --batch_size  1  --width  1024  --height  2048  --input_images_dir images  --output_dir results
 
 
3.3.2 grpc傳輸限制
默認情況下,grpc只可以傳輸4M的文件,這對於我們以圖像作為目的的推理來說,是不夠的。因此,如果不做設置,會出現以下問題:
Request shape ( 1, 3, 1024, 2048)
( 1, 3, 1024, 2048)
Traceback (most recent call last) :
  File "sky_detection.py", line 79, in <module >
    result = stub.Predict(request, 10. 0
  File "/usr/local/lib64/python3.6/site-packages/grpc/_channel.py", line 690, in __call__
    return _end_unary_response_blocking(state, call, False, None)
  File "/usr/local/lib64/python3.6/site-packages/grpc/_channel.py", line 592, in _end_unary_response_blocking
    raise _Rendezvous(state, None, None, deadline)
grpc._channel._Rendezvous : <_Rendezvous of RPC that terminated with :
    status = StatusCode.RESOURCE_EXHAUSTED
    details = "Received message larger than max (8388653 vs. 4194304)"
    debug_error_string = "{"created ":"@ 1602672141. 715481155 ","description " :"Received message larger than max (8388653 vs. 4194304) ","file ":"src /core /ext /filters /message_size /message_size_filter.cc ","file_line ":190,"grpc_status ":8}"
 
這個問題,經過管理員提醒

 

@jsxyhelu The limit on the server side is actually 1GB. Your logs indicate 4MB.
It seems to be client side restriction.
Could you try the following settings :
options = [('grpc.max_receive_message_length'100 * 1024 * 1024),('grpc.max_send_message_length'100 * 1024 * 1024)]
channel = grpc.insecure_channel(server_url, options = options)

 

 
嘗試進行解決。 具體來說,就是采用這樣的修改:
options = [( 'grpc.max_receive_message_length'100 *  1024 *  1024),( 'grpc.max_send_message_length'100 *  1024 *  1024)]
# this may make sense
channel = grpc.insecure_channel( "{}:{}".format(args[ 'grpc_address'],args[ 'grpc_port']),options = options)
stub = prediction_service_pb2_grpc.PredictionServiceStub(channel)
 
3.3.3 獲得模型名稱
接口代碼中,需要寫明模型的Output name。這個名稱比較隱晦,文檔中是沒有的(我沒有找到)。解決的方法是需要通過“ get_serving_meta.py”(來自 https://github.com/openvinotoolkit/model_server/) 獲得輸出模型的具體名稱,比如在模型已經啟動(docker run)的情況下運行:
root@VM - 0 - 13 - centos tmp] # python3 get_serving_meta.py --grpc_port 9000 --model_name semantic-segmentation-adas --model_version 1
2020 - 10 - 17 07 : 03 : 10 . 395324 : W tensorflow / stream_executor / platform / default / dso_loader.cc : 59 ] Could not load dynamic library 'libcudart.so.10.1' ; dlerror : libcudart.so. 10 . 1 : cannot open shared object file : No such file or directory
2020 - 10 - 17 07 : 03 : 10 . 395363 : I tensorflow / stream_executor / cuda / cudart_stub.cc : 29 ] Ignore above cudart dlerror if you do not have a GPU set up on your machine.
Getting model metadata for model : semantic - segmentation - adas
Inputs metadata :
    Input name : data; shape : [ 1 , 3 , 1024 , 2048 ]; dtype : DT_FLOAT
Outputs metadata :
    Output name : 4455.1 ; shape : [ 1 , 1 , 1024 , 2048 ]; dtype : DT_INT32
那么輸出就叫做4455.1,這個一個內部的名稱。后面如果有需要,可以將這一步獲取名稱的操作變成內部獲取,具體來說就是 整編 get_serving_meta.py的內容到接口文件,這里為了方便說明原理先不這樣做。
 
3.3.4 接口文件經過大量改寫
經過上面的准備,接口文件正確運行的先決條件已經具備。下面需要具體修正接口文件。主要是一些存儲和配色功能。
#
# Copyright (c) 2019-2020 Intel Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# update 2020/10/17

import argparse
import cv2
import datetime
import grpc
import numpy  as np
import os
from tensorflow  import make_tensor_proto, make_ndarray
from tensorflow_serving.apis  import predict_pb2
from tensorflow_serving.apis  import prediction_service_pb2_grpc
from client_utils  import print_statistics

classes_color_map = [
    ( 150150150),
    ( 5855169),
    ( 2115117),
    ( 1578044),
    ( 2395189),
    ( 21013334),
    ( 76226202),
    ( 101138127),
    ( 22391182),
    ( 80128113),
    ( 23515555),
    ( 44151243),
    ( 15980170),
    ( 23920844),
    ( 1285051),
    ( 82141193),
    ( 910710),
    ( 22390142),
    ( 5024883),
    ( 178101130),
    ( 7130204)
]

def load_image(file_path):
    img = cv2.imread(file_path)   # BGR color format, shape HWC
    img = cv2.resize(img, (args[ 'width'], args[ 'height']))
    img = img.transpose( 2, 0, 1).reshape( 1, 3,args[ 'height'],args[ 'width'])
     # change shape to NCHW
     return img


parser = argparse.ArgumentParser(description= 'Demo for sky detection requests via TFS gRPC API.'
                                              'analyses input images and saves with with detected skys.'
                                              'it relies on model semantic-segmentation...')

parser.add_argument( '--input_images_dir', required= False, help= 'Directory with input images', default= "images/people")
parser.add_argument( '--output_dir', required= False, help= 'Directory for staring images with detection results', default= "results")
parser.add_argument( '--batch_size', required= False, help= 'How many images should be grouped in one batch', default= 1, type=int)
parser.add_argument( '--width', required= False, help= 'How the input image width should be resized in pixels', default= 1200, type=int)
parser.add_argument( '--height', required= False, help= 'How the input image width should be resized in pixels', default= 800, type=int)
parser.add_argument( '--grpc_address',required= False, default= 'localhost',  help= 'Specify url to grpc service. default:localhost')
parser.add_argument( '--grpc_port',required= False, default= 9000, help= 'Specify port to grpc service. default: 9000')

args = vars(parser.parse_args())

options = [('grpc.max_receive_message_length', 100 * 1024 * 1024),('grpc.max_send_message_length', 100 * 1024 * 1024)]
# this may make sense
channel = grpc.insecure_channel("{}:{}".format(args['grpc_address'],args['grpc_port']),options = options)
stub = prediction_service_pb2_grpc.PredictionServiceStub(channel)

files = os.listdir(args[ 'input_images_dir'])
batch_size = args[ 'batch_size']
print(files)


imgs = np.zeros(( 0, 3,args[ 'height'],args[ 'width']), np.dtype( '<f'))
for i  in files:
    img = load_image(os.path.join(args[ 'input_images_dir'], i))
    imgs = np.append(imgs, img, axis= 0)   # contains all imported images

print( 'Start processing {} iterations with batch size {}'.format(len(files)//batch_size , batch_size))

iteration =  0
processing_times = np.zeros(( 0),int)

for x  in range( 0, imgs.shape[ 0] - batch_size +  1, batch_size):
    iteration +=  1
    request = predict_pb2.PredictRequest()
    request.model_spec.name =  "semantic-segmentation-adas"
    img = imgs[x:(x + batch_size)]
    print( "\nRequest shape", img.shape)
    request.inputs[ "data"].CopyFrom(make_tensor_proto(img, shape=(img.shape)))
    start_time = datetime.datetime.now()
    result = stub.Predict(request,  10.0)     # result includes a dictionary with all model outputs print(img.shape) 
     output = make_ndarray(result.outputs["4455.1"])

     for y  in range( 0,img.shape[ 0]):   # iterate over responses from all images in the batch
        img_out = output[y,:,:,:]
        print( "image in batch item",y,  ", output shape",img_out.shape)
        img_out = img_out.transpose( 1, 2, 0)
        print( "saving result to",os.path.join(args[ 'output_dir'],str(iteration)+ "_"+str(y)+ '.jpg'))
        out_h, out_w,_ = img_out.shape
        print(out_h)
        print(out_w)
         for batch, data in enumerate(output):
            classes_map = np.zeros(shape=(out_h, out_w, 3), dtype=np.int)
            for i in range(out_h):
                for j in range(out_w):
                    if len(data[:, i, j]) == 1:
                        pixel_class = int(data[:, i, j])
                    else:
                        pixel_class = np.argmax(data[:, i, j])
                    classes_map[i, j, :] = classes_color_map[min(pixel_class, 20)]
            cv2.imwrite(os.path.join(args['output_dir'],str(iteration)+"_"+str(batch)+'.jpg'),classes_map)

我較facedetection.py進行了修改的地方都進行了標黃。而后運行
python3 sky_detection.py --batch_size 1 --width 2048  --height 1024 --input_images_dir images --output_dir results
注意這里是   -- width  2018   -- height  1024  。 運行成功的回顯
[root@VM - 0 - 13 -centos tmp] # python3 sky_detection.py --batch_size 1 --width 2048 --height 1024 --input_images_dir images --output_dir results
2020 - 10 - 17 07 : 46 : 20. 942953 : W tensorflow /stream_executor /platform /default /dso_loader.cc : 59] Could not load dynamic library 'libcudart.so.10.1'; dlerror : libcudart.so. 10. 1 : cannot open shared object file : No such file or directory
2020 - 10 - 17 07 : 46 : 20. 943164 : I tensorflow /stream_executor /cuda /cudart_stub.cc : 29] Ignore above cudart dlerror if you do not have a GPU set up on your machine.
[ 'sky9.jpg']
Start processing 1 iterations with batch size 1

Request shape ( 1, 3, 1024, 2048)
image in batch item 0 , output shape ( 1, 1024, 2048)
saving result to results / 1_0.jpg
1024
2048
 
結果我們來看result,雖然這種結果被拉變形了(1024X2048),但是很多細節還是被區分出來了(紅圈標注),曝光的地方也確實比較難以識別,出現了明顯錯誤(黃圈標注)
和原圖對比。
 
   
 
這樣一個結果,是目前不經過專門訓練,使用通用模型能夠獲得的最好結果。這個比對結果能為最后的“天空替換”做良好支持。
 





附件列表

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM