OpenCV 4.12.0
開源計算機視覺
載入中...
搜尋中...
無匹配項
TensorFlow 分割模型的轉換及使用 OpenCV 啟動

目標

在本教程中,您將學習如何

  • 轉換 TensorFlow (TF) 分割模型
  • 使用 OpenCV 執行轉換後的 TensorFlow 模型
  • 獲取 TensorFlow 和 OpenCV DNN 模型的評估結果

我們將透過 DeepLab 架構的示例來探討上述要點。

簡介

透過 OpenCV API 轉換 TensorFlow 分類和分割模型所涉及的關鍵概念幾乎相同,除了圖最佳化階段。將 TensorFlow 模型轉換為 cv.dnn.Net 的初始步驟是獲取凍結的 TF 模型圖。凍結圖定義了模型圖結構與所需變數的保留值的組合,例如權重。通常,凍結圖儲存在 protobuf (.pb) 檔案中。要使用 cv.dnn.readNetFromTensorflow 讀取生成的分割模型 .pb 檔案,需要使用 TF 圖轉換工具修改該圖。

實踐

在本部分中,我們將介紹以下幾點

  1. 建立 TF 分類模型轉換管道並提供推理
  2. 評估和測試 TF 分類模型

如果您只想執行評估或測試模型管道,“模型轉換管道”教程部分可以跳過。

模型轉換管道

本小節中的程式碼位於 dnn_model_runner 模組中,可以使用以下行執行

python -m dnn_model_runner.dnn_conversion.tf.segmentation.py_to_py_deeplab

TensorFlow 分割模型可以在 TensorFlow 研究模型部分中找到,其中包含基於已釋出的研究論文的模型實現。我們將從以下連結檢索包含預訓練 TF DeepLabV3 的存檔

http://download.tensorflow.org/models/deeplabv3_mnv2_pascal_trainval_2018_01_29.tar.gz

完整的凍結圖獲取管道在 deeplab_retrievement.py 中描述

def get_deeplab_frozen_graph()
# 定義要下載的模型路徑
models_url = 'http://download.tensorflow.org/models/'
mobilenetv2_voctrainval = 'deeplabv3_mnv2_pascal_trainval_2018_01_29.tar.gz'
# 構建要下載的模型連結
model_link = models_url + mobilenetv2_voctrainval
try:
urllib.request.urlretrieve(model_link, mobilenetv2_voctrainval)
except Exception
print("TF DeepLabV3 未檢索:{}".format(model_link))
return
tf_model_tar = tarfile.open(mobilenetv2_voctrainval)
# 迭代獲得的模型存檔
for model_tar_elem in tf_model_tar.getmembers()
# 檢查模型存檔是否包含凍結圖
if TF_FROZEN_GRAPH_NAME in os.path.basename(model_tar_elem.name)
# 提取凍結圖
tf_model_tar.extract(model_tar_elem, FROZEN_GRAPH_PATH)
tf_model_tar.close()

執行此指令碼後

python -m dnn_model_runner.dnn_conversion.tf.segmentation.deeplab_retrievement

我們將在 deeplab/deeplabv3_mnv2_pascal_trainval 中獲得 frozen_inference_graph.pb

在使用 OpenCV 載入網路之前,需要最佳化提取的 frozen_inference_graph.pb。為了最佳化圖,我們使用帶有預設引數的 TF TransformGraph

DEFAULT_OPT_GRAPH_NAME = "optimized_frozen_inference_graph.pb"
DEFAULT_INPUTS = "sub_7"
DEFAULT_OUTPUTS = "ResizeBilinear_3"
DEFAULT_TRANSFORMS = "remove_nodes(op=Identity)" \
" merge_duplicate_nodes" \
" strip_unused_nodes" \
" fold_constants(ignore_errors=true)" \
" fold_batch_norms" \
" fold_old_batch_norms"
def optimize_tf_graph(
in_graph,
out_graph=DEFAULT_OPT_GRAPH_NAME,
inputs=DEFAULT_INPUTS,
outputs=DEFAULT_OUTPUTS,
transforms=DEFAULT_TRANSFORMS,
is_manual=True,
was_optimized=True
):
# ...
tf_opt_graph = TransformGraph(
tf_graph,
inputs,
outputs,
transforms
)

要執行圖最佳化過程,執行以下行

python -m dnn_model_runner.dnn_conversion.tf.segmentation.tf_graph_optimizer --in_graph deeplab/deeplabv3_mnv2_pascal_trainval/frozen_inference_graph.pb

結果,deeplab/deeplabv3_mnv2_pascal_trainval 目錄將包含 optimized_frozen_inference_graph.pb

獲得模型圖後,讓我們檢查以下列出的步驟

  1. 讀取 TF frozen_inference_graph.pb
  2. 使用 OpenCV API 讀取最佳化的 TF 凍結圖
  3. 準備輸入資料
  4. 提供推理
  5. 從預測中獲取彩色掩碼
  6. 視覺化結果
# 從獲得的凍結圖獲取 TF 模型圖
deeplab_graph = read_deeplab_frozen_graph(deeplab_frozen_graph_path)
# 使用 OpenCV API 讀取 DeepLab 凍結圖
opencv_net = cv2.dnn.readNetFromTensorflow(opt_deeplab_frozen_graph_path)
print("OpenCV 模型已成功讀取。模型層:\n", opencv_net.getLayerNames())
# 獲取處理後的影像
original_img_shape, tf_input_blob, opencv_input_img = get_processed_imgs("test_data/sem_segm/2007_000033.jpg")
# 獲取 OpenCV DNN 預測
opencv_prediction = get_opencv_dnn_prediction(opencv_net, opencv_input_img)
# 獲取 TF 模型預測
tf_prediction = get_tf_dnn_prediction(deeplab_graph, tf_input_blob)
# 獲取 PASCAL VOC 類和顏色
pascal_voc_classes, pascal_voc_colors = read_colors_info("test_data/sem_segm/pascal-classes.txt")
# 獲取彩色分割掩碼
opencv_colored_mask = get_colored_mask(original_img_shape, opencv_prediction, pascal_voc_colors)
tf_colored_mask = get_tf_colored_mask(original_img_shape, tf_prediction, pascal_voc_colors)
# 獲取 PASCAL VOC 顏色的調色盤
color_legend = get_legend(pascal_voc_classes, pascal_voc_colors)
cv2.imshow('TensorFlow 彩色掩碼', tf_colored_mask)
cv2.imshow('OpenCV DNN 彩色掩碼', opencv_colored_mask)
cv2.imshow('顏色圖例', color_legend)

為了提供模型推理,我們將使用來自 PASCAL VOC 驗證資料集的下圖

PASCAL VOC img

目標分割結果是

PASCAL VOC ground truth

對於 PASCAL VOC 顏色解碼及其與預測掩碼的對映,我們還需要 pascal-classes.txt 檔案,其中包含 PASCAL VOC 類的完整列表和相應的顏色。

讓我們以預訓練的 TF DeepLabV3 MobileNetV2 為例,更深入地瞭解每個步驟

  • 讀取 TF frozen_inference_graph.pb
# 初始化 deeplab 模型圖
model_graph = tf.Graph()
# 獲取
with tf.io.gfile.GFile(frozen_graph_path, 'rb') as graph_file
tf_model_graph = GraphDef()
tf_model_graph.ParseFromString(graph_file.read())
with model_graph.as_default()
tf.import_graph_def(tf_model_graph, name='')
  • 使用 OpenCV API 讀取最佳化的 TF 凍結圖
# 使用 OpenCV API 讀取 DeepLab 凍結圖
opencv_net = cv2.dnn.readNetFromTensorflow(opt_deeplab_frozen_graph_path)
  • 使用 cv2.dnn.blobFromImage 函式準備輸入資料
# 讀取影像
input_img = cv2.imread(img_path, cv2.IMREAD_COLOR)
input_img = input_img.astype(np.float32)
# 預處理 TF 模型輸入的影像
tf_preproc_img = cv2.resize(input_img, (513, 513))
tf_preproc_img = cv2.cvtColor(tf_preproc_img, cv2.COLOR_BGR2RGB)
# 定義 OpenCV DNN 的預處理引數
mean = np.array([1.0, 1.0, 1.0]) * 127.5
scale = 1 / 127.5
# 準備輸入 blob 以適應模型輸入
# 1. 減去均值
# 2. 縮放以將畫素值設定為 0 到 1
input_blob = cv2.dnn.blobFromImage(
image=input_img,
scalefactor=scale,
size=(513, 513), # 影像目標大小
mean=mean,
swapRB=True, # BGR -> RGB
crop=False # 中心裁剪
)

請注意 cv2.dnn.blobFromImage 函式中的預處理順序。首先,減去平均值,然後將畫素值乘以定義的比例。因此,為了重現 TF 影像預處理管道,我們將 mean 乘以 127.5。另一個重點是 TF DeepLab 的影像預處理。要將影像傳遞到 TF 模型中,我們只需要構建一個合適的形狀,其餘的影像預處理在 feature_extractor.py 中描述,並將自動呼叫。

  • 提供 OpenCV cv.dnn_Net 推理
# 設定 OpenCV DNN 輸入
opencv_net.setInput(preproc_img)
# OpenCV DNN 推理
out = opencv_net.forward()
print("OpenCV DNN 分割預測:\n")
print("* 形狀:", out.shape)
# 獲取預測類的 ID
out_predictions = np.argmax(out[0], axis=0)

在上述程式碼執行後,我們將獲得以下輸出

OpenCV DNN 分割預測
* shape: (1, 21, 513, 513)

21 個預測通道中的每一個,其中 21 代表 PASCAL VOC 類的數量,都包含機率,指示畫素對應於 PASCAL VOC 類的可能性。

  • 提供 TF 模型推理
preproc_img = np.expand_dims(preproc_img, 0)
# 初始化 TF 會話
tf_session = Session(graph=model_graph)
input_tensor_name = "ImageTensor:0",
output_tensor_name = "SemanticPredictions:0"
# 執行推理
out = tf_session.run(
output_tensor_name,
feed_dict={input_tensor_name: [preproc_img]}
)
print("TF 分割模型預測:\n")
print("* 形狀:", out.shape)

TF 推理結果如下

TF 分割模型預測
* shape: (1, 513, 513)

TensorFlow 預測包含相應 PASCAL VOC 類的索引。

  • 將 OpenCV 預測轉換為彩色掩碼
mask_height = segm_mask.shape[0]
mask_width = segm_mask.shape[1]
img_height = original_img_shape[0]
img_width = original_img_shape[1]
# 將掩碼值轉換為 PASCAL VOC 顏色
processed_mask = np.stack([colors[color_id] for color_id in segm_mask.flatten()])
# 將掩碼重塑為 3 通道影像
processed_mask = processed_mask.reshape(mask_height, mask_width, 3)
processed_mask = cv2.resize(processed_mask, (img_width, img_height), interpolation=cv2.INTER_NEAREST).astype(
np.uint8)
# 將彩色掩碼從 BGR 轉換為 RGB
processed_mask = cv2.cvtColor(processed_mask, cv2.COLOR_BGR2RGB)

在此步驟中,我們將分割掩碼的機率與預測類的相應顏色進行對映。讓我們看一下結果

Color Legend

OpenCV Colored Mask

  • 將 TF 預測轉換為彩色掩碼
colors = np.array(colors)
processed_mask = colors[segm_mask[0]]
img_height = original_img_shape[0]
img_width = original_img_shape[1]
processed_mask = cv2.resize(processed_mask, (img_width, img_height), interpolation=cv2.INTER_NEAREST).astype(
np.uint8)
# 將彩色掩碼從 BGR 轉換為 RGB,以便與 PASCAL VOC 顏色相容
processed_mask = cv2.cvtColor(processed_mask, cv2.COLOR_BGR2RGB)

結果是

TF Colored Mask

結果,我們得到了兩個相等的分割掩碼。

模型評估

dnn/samples 中提出的 dnn_model_runner 模組允許在 PASCAL VOC 資料集上執行完整的評估管道,並測試 DeepLab MobileNet 模型的執行情況。

評估模式

以下行表示在評估模式下執行模組

python -m dnn_model_runner.dnn_conversion.tf.segmentation.py_to_py_segm

該模型將被讀入 OpenCV cv.dnn_Net 物件。TF 和 OpenCV 模型的評估結果(畫素精度、平均 IoU、推理時間)將被寫入日誌檔案。推理時間值也將以圖表形式顯示,以概括獲得的模型資訊。

必要的評估配置在 test_config.py 中定義

@dataclass
class TestSegmConfig
frame_size: int = 500
img_root_dir: str = "./VOC2012"
img_dir: str = os.path.join(img_root_dir, "JPEGImages/")
img_segm_gt_dir: str = os.path.join(img_root_dir, "SegmentationClass/")
# 縮減的 val:https://github.com/shelhamer/fcn.berkeleyvision.org/blob/master/data/pascal/seg11valid.txt
segm_val_file: str = os.path.join(img_root_dir, "ImageSets/Segmentation/seg11valid.txt")
colour_file_cls: str = os.path.join(img_root_dir, "ImageSets/Segmentation/pascal-classes.txt")

這些值可以根據選擇的模型管道進行修改。

測試模式

以下行表示在測試模式下執行模組,該模式提供了模型推理的步驟

python -m dnn_model_runner.dnn_conversion.tf.segmentation.py_to_py_segm --test True --default_img_preprocess <True/False> --evaluate False

此處 default_img_preprocess 鍵定義您是否要使用一些特定值來引數化模型測試過程,或者使用預設值,例如 scalemeanstd

測試配置在 test_config.py TestSegmModuleConfig 類中表示

@dataclass
class TestSegmModuleConfig
segm_test_data_dir: str = "test_data/sem_segm"
test_module_name: str = "segmentation"
test_module_path: str = "segmentation.py"
input_img: str = os.path.join(segm_test_data_dir, "2007_000033.jpg")
model: str = ""
frame_height: str = str(TestSegmConfig.frame_size)
frame_width: str = str(TestSegmConfig.frame_size)
scale: float = 1.0
mean: List[float] = field(default_factory=lambda: [0.0, 0.0, 0.0])
std: List[float] = field(default_factory=list)
crop: bool = False
rgb: bool = True
classes: str = os.path.join(segm_test_data_dir, "pascal-classes.txt")

預設影像預處理選項在 default_preprocess_config.py 中定義

tf_segm_input_blob = {
"scale": str(1 / 127.5),
"mean": ["127.5", "127.5", "127.5"],
"std": [],
"crop": "False",
"rgb": "True"
}

模型測試的基礎在 samples/dnn/segmentation.py 中表示。segmentation.py 可以獨立執行,並提供轉換後的模型 --input 和為 cv2.dnn.blobFromImage 填充的引數。

要從頭開始重現 “模型轉換管道” 中描述的帶有 dnn_model_runner 的 OpenCV 步驟,請執行以下行

python -m dnn_model_runner.dnn_conversion.tf.segmentation.py_to_py_segm --test True --default_img_preprocess True --evaluate False