項目背景
鋼鐵廠生產鋼筋的過程中會存在部分鋼筋長度超限的問題,如果不進行處理,容易造成機械臂損傷。因此,需要通過質檢流程,篩選出存在長度超限問題的鋼筋批次,並進行預警。傳統的處理方式是人工覈查,該方式一方面增加了人工成本,降低了生產效率;另一方面也要求工人師傅對業務比較熟練,能夠準確地判斷鋼筋長度是否超限,且該方法可能存在一定的誤判率。在AI時代,利用深度學習技術,可以實現端到端全自動的鋼筋長度超限監控,從而降低人工成本,提高生產效率。整體技術方案可以歸納爲如下步驟:
-
在鋼筋一側安裝攝像頭,拍攝圖像;
-
利用圖像分割技術提取鋼筋掩膜;
-
根據攝像頭位置和角度確定長度界限;
-
最後根據該長度界限和鋼筋分割範圍的幾何關係判斷本批次鋼筋是否超限。
鋼筋長度超限監控整體流程
鋼筋超限監控問題可以轉換爲圖像分割後的幾何判斷問題。爲了實現圖像分割,我們使用提供了全流程分割方案的飛槳圖像分割套件 PaddleSeg,只需簡單地修改配置文件,就可以進行模型訓練,獲得高精度的分割效果。進一步地,我們挑選使用精度和速度平衡的 PP-LiteSeg 模型,保證在實現高精度的同時,滿足工業部署的要求。
安裝環境
使用 PaddleSeg 套件,我們需要準備如下環境:
接下來,使用如下命令安裝 PaddleSeg 以及相應的依賴:
git clone --branch release/2.6 --depth 1 https://gitee.com/PaddlePaddle/PaddleSeg.git
cd PaddleSeg
pip install -r requirements.txt
數據處理
由於鋼筋長度超限檢測數據集是使用圖像標註工具 LabelMe 標註的,其數據格式與 PaddleSeg 支持的格式不同,因此可藉助 PaddleSeg 中 tools 目錄下的腳本 labelme2seg.py,將 LabelMe 格式標註轉換成 PaddleSeg 支持的格式。
python tools/labelme2seg.py ~/data/dataset
接下來,使用 PaddleSeg 提供的腳本(split_dataset_list.py)將數據集劃分爲訓練集、驗證集和測試集。
python tools/split_dataset_list.py ~/data/dataset . annotations --split 0.7 0.15 0.15
模型訓練
此處我們選擇輕量級語義分割模型 PP-LiteSeg 模型,對鋼筋進行分割。具體介紹可參考 PP-LiteSeg 的 README 說明文件。
- 說明文件鏈接
https://github.com/PaddlePaddle/PaddleSeg/tree/release/2.7/configs/pp_liteseg
爲了在自定義數據集上使用 PP-LiteSeg 模型,需要對 PaddleSeg 提供的默認配置文件(PaddleSeg/configs/pp_liteseg/pp_liteseg_stdc1_cityscapes_1024x512_scale0.5_160k.yml)進行輕微修改。
如下所示,添加自定義數據集路徑、類別數等信息:
batch_size: 4 # total: 4*4
iters: 2000
optimizer:
type: sgd
momentum: 0.9
weight_decay: 5.0e-4
lr_scheduler:
type: PolynomialDecay
end_lr: 0
power: 0.9
warmup_iters: 100
warmup_start_lr: 1.0e-5
learning_rate: 0.005
loss:
types:
- type: OhemCrossEntropyLoss
min_kept: 130000 # batch_size * 1024 * 512 // 16
- type: OhemCrossEntropyLoss
min_kept: 130000
- type: OhemCrossEntropyLoss
min_kept: 130000
coef: [1, 1, 1]
train_dataset:
type: Dataset
dataset_root: /home/aistudio/data/dataset
train_path: /home/aistudio/data/dataset/train.txt
num_classes: 2
transforms:
- type: ResizeStepScaling
min_scale_factor: 0.125
max_scale_factor: 1.5
scale_step_size: 0.125
- type: RandomPaddingCrop
crop_size: [1024, 512]
- type: RandomHorizontalFlip
- type: RandomDistort
brightness_range: 0.5
contrast_range: 0.5
saturation_range: 0.5
- type: Normalize
mode: train
val_dataset:
type: Dataset
dataset_root: /home/aistudio/data/dataset
val_path: /home/aistudio/data/dataset/val.txt
num_classes: 2
transforms:
- type: Normalize
mode: val
test_config:
aug_eval: True
scales: 0.5
model:
type: PPLiteSeg
backbone:
type: STDC1
pretrained: https://bj.bcebos.com/paddleseg/dygraph/PP_STDCNet1.tar.gz
arm_out_chs: [32, 64, 128]
seg_head_inter_chs: [32, 64, 64]
接下來,開始執行訓練:
python3 train.py --config /home/aistudio/work/pp_liteseg_stdc1.yml \
--use_vdl \
--save_dir output/mask_iron \
--save_interval 500 \
--log_iters 100 \
--num_workers 8 \
--do_eval \
--keep_checkpoint_max 10
使用 PaddleSeg 訓練過程中可能會出現報錯,例如,one_hot_kernel 相關的報錯:
Error: /paddle/paddle/phi/kernels/gpu/one_hot_kernel.cu:38 Assertion `p_in_data[idx] >= 0 && p_in_data[idx] < depth` failed. Illegal index value, Input(input) value should be greater than or equal to 0, and less than depth [1], but received [1].
這裏需要注意類別是否正確設置,考慮背景類是否添加。one_hot_kernel 另一種報錯:
Error: /paddle/paddle/phi/kernels/gpu/one_hot_kernel.cu:38 Assertion `p_in_data[idx] >= 0 && p_in_data[idx] < depth` failed. Illegal index value, Input(input) value should be greater than or equal to 0, and less than depth [5], but received [-1].
此時需要注意 mask 中標籤是否超過 [0, num_classes + 1] 的範圍。訓練完成後,可使用模型評估腳本對訓練好的模型進行評估:
python val.py \
--config /home/aistudio/work/pp_liteseg_stdc1.yml \
--model_path output/mask_iron/best_model/model.pdparams
輸出結果爲:
2023-03-06 11:22:09 [INFO] [EVAL] #Images: 32 mIoU: 0.9858 Acc: 0.9947 Kappa: 0.9857 Dice: 0.9928
2023-03-06 11:22:09 [INFO] [EVAL] Class IoU:
[0.993 0.9787]
2023-03-06 11:22:09 [INFO] [EVAL] Class Precision:
[0.9969 0.9878]
2023-03-06 11:22:09 [INFO] [EVAL] Class Recall:
[0.996 0.9906]
由評估輸出可見,模型性能爲 mIoU:0.9858,Acc:0.9947,能夠滿足實際工業場景需求。
模型預測
使用 predict.py 可用來查看具體樣本的切割樣本效果。
python predict.py \
--config /home/aistudio/work/pp_liteseg_stdc1.yml \
--model_path output/mask_iron/best_model/model.pdparams \
--image_path /home/aistudio/data/dataset/ec539f77-7061-4106-9914-8d66f450234d.jpg \
--save_dir output/result
預測的結果如下所示。
import matplotlib.pyplot as plt
import cv2
im = cv2.imread("/home/aistudio/work/PaddleSeg/output/result/pseudo_color_prediction/ec539f77-7061-4106-9914-8d66f450234d.png")
# cv2.imshow("result", im)
plt.imshow(cv2.cvtColor(im, cv2.COLOR_BGR2RGB))
plt.figure()
im = cv2.imread("/home/aistudio/work/PaddleSeg/output/result/added_prediction/ec539f77-7061-4106-9914-8d66f450234d.jpg")
plt.imshow(cv2.cvtColor(im, cv2.COLOR_BGR2RGB))
接下來,利用預測的結果,並採用最大聯通域處理後,判斷鋼筋是否超限。
import cv2
def largestcomponent(img_path, threshold=None):
"""
Filter the input image_path with threshold, only component that have area larger than threshold will be kept.
Arg:
img_path: path to a binary img
threshold: connected componet with area larger than this value will be kept
"""
binary = cv2.imread(img_path, cv2.IMREAD_GRAYSCALE)
binary[binary == binary.min()] = 0
binary[binary == binary.max()] = 255
assert (
binary.max() == 255 and binary.min() == 0
), "The input need to be a binary image, but the maxval in image is {} and the minval in image is {}".format(
binary.max(), binary.min()
)
if threshold is None:
threshold = binary.shape[0] * binary.shape[1] * 0.01
contours, hierarchy = cv2.findContours(
binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE
)
for k in range(len(contours)):
if cv2.contourArea(contours[k]) < threshold:
cv2.fillPoly(binary, [contours[k]], 0)
cv2.imwrite(img_path.split(".")[0] + "_postprocessed.png", binary)
此處,我們可以對比最大聯通域處理前後的差別,可以發現濾除了小的聯通區域。
prediction = "/home/aistudio/work/PaddleSeg/output/result/pseudo_color_prediction/ec539f77-7061-4106-9914-8d66f450234d.png"
# prediction = "/home/aistudio/work/PaddleSeg/output/result/pseudo_color_prediction/20220705-153804.png"
largestcomponent(prediction)
before_image = cv2.imread(prediction)
after_image = cv2.imread(prediction.replace(".png", "_postprocessed.png"))
plt.subplot(1, 2, 1)
plt.imshow(cv2.cvtColor(before_image, cv2.COLOR_BGR2RGB))
plt.subplot(1, 2, 2)
plt.imshow(cv2.cvtColor(after_image, cv2.COLOR_BGR2RGB))
plt.show()
判斷鋼筋是否超限:
def excesslimit(image_path, direction="right", position=0.6):
"""
Automatically tells if the steel bar excess manually set position.
Arg:
img_path: path to a binary img
direction: which part of the img is the focused area for detecting bar excession.
position: the ratio of the position of the line to the width of the image.
Return:
excess: whether there is steel wheel excess the limit line.
excess_potion: the portion of the excess steel bar to the whole bar.
"""
excess_portion = 0.0
binary = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
binary[binary == binary.min()] = 0
binary[binary == binary.max()] = 255
assert (
binary.max() == 255 and binary.min() == 0
), "The input need to be a binary image, but the maxval in image is {} and the minval in image is {}".format(
binary.max(), binary.min()
)
assert (
direction == "left" or direction == "right"
), "The direction indicates the side of image that iron excess, it should be 'right' or 'left', but we got {}".format(
direction
)
assert (
position > 0 and position < 1
), "The position indicates the relative position to set the line, it should bigger than 0 and smaller than 1, but we got {}".format(
position
)
img_pos = int(binary.shape[1] * position)
if direction == "right":
if binary[:, img_pos:].sum() > 0:
excess_portion = binary[:, img_pos:].sum() / binary.sum()
binary[:, img_pos : img_pos + 3] = 255
else:
if binary[:, :img_pos].sum() > 0:
excess_portion = binary[:, :img_pos].sum() / binary.sum()
binary[:, img_pos - 3 : img_pos] = 255
print(
"The iron is {}excessed in {}, and the excess portion is {}".format(
["", "not "][excess_portion == 0], image_path, excess_portion
)
)
# cv2.imwrite(image_path.split(".")[0] + "_fullpostprocessed.png", binary)
cv2.imwrite(image_path.replace("_postprocessed.png", "_fullpostprocessed.png"), binary)
return excess_portion > 0, excess_portion
對預測結果批量判斷是否超限。
import os
import glob
output_dir = "/home/aistudio/work/PaddleSeg/output"
pseudo_color_result = os.path.join(output_dir, 'result/pseudo_color_prediction')
os.system(f"rm {pseudo_color_result}/*_*postprocessed.*")
for img_path in glob.glob(os.path.join(pseudo_color_result, "*.png")):
largestcomponent(img_path)
postproc_img_path = img_path.replace(".png", "_postprocessed.png")
excesslimit(postproc_img_path, "left", 0.3)
可視化後處理結果。
im = cv2.imread("/home/aistudio/work/PaddleSeg/output/result/pseudo_color_prediction/ec539f77-7061-4106-9914-8d66f450234d_fullpostprocessed.png")
# cv2.imshow("result", im)
plt.imshow(im)
模型推理與部署
我們可以選擇使用飛槳原生推理庫 Paddle Inference 推理。首先將訓練好的模型導出爲 Paddle Inference 模型。
export CUDA_VISIBLE_DEVICES=0 # Set a usable GPU.
# If on windows, Run the following command
# set CUDA_VISIBLE_DEVICES=0
python export.py \
--config /home/aistudio/work/pp_liteseg_stdc1.yml \
--model_path output/mask_iron/best_model/model.pdparams \
--save_dir output/inference
接下來使用推理模型預測。
python deploy/python/infer.py \
--config output/inference/deploy.yaml \
--save_dir output/infer_result \
--image_path /home/aistudio/data/dataset/bcd33bcd-d48c-4409-940d-51301c8a7697.jpg
最後,根據模型輸出,判斷鋼筋是否超限,可視化判斷結果。
img_path = "/home/aistudio/work/PaddleSeg/output/infer_result/bcd33bcd-d48c-4409-940d-51301c8a7697.png"
largestcomponent(img_path)
postproc_img_path = img_path.replace(".png", "_postprocessed.png")
excesslimit(postproc_img_path, "right", 0.5)
img_path = "/home/aistudio/work/PaddleSeg/output/infer_result/bcd33bcd-d48c-4409-940d-51301c8a7697_fullpostprocessed.png"
im = cv2.imread(img_path)
plt.imshow(im)
我們也可以使用 FastDeploy 進行部署。FastDeploy 是一款全場景、易用靈活、極致高效的AI推理部署工具。其提供開箱即用的雲邊端部署體驗,支持超過160個文本、視覺、語音和跨模態模型,並可實現端到端的推理性能優化。此外,其還可實現包括圖像分類、物體檢測、圖像分割、人臉檢測、人臉識別、關鍵點檢測、摳圖、OCR、NLP和TTS等任務,滿足開發者多場景、多硬件、多平臺的產業部署需求。
通過如下命令就可以非常方便地安裝 FastDeploy。
pip install fastdeploy-gpu-python -f https://www.paddlepaddle.org.cn/whl/fastdeploy.html
使用 FastDeploy 進行預測也十分簡單:
import fastdeploy as fd
model_file = "/home/aistudio/work/PaddleSeg/output/inference/model.pdmodel"
params_file = "/home/aistudio/work/PaddleSeg/output/inference/model.pdiparams"
infer_cfg_file = "/home/aistudio/work/PaddleSeg/output/inference/deploy.yaml"
# 模型推理的配置信息
option = fd.RuntimeOption()
model = fd.vision.segmentation.PaddleSegModel(model_file, params_file, infer_cfg_file, option)
# 預測結果
import cv2
img_path = "/home/aistudio/data/dataset/8f7fcf0a-a3ea-41f2-9e67-4cbaa61238a4.jpg"
im = cv2.imread(img_path)
result = model.predict(im)
print(result)
我們也可以使用 FastDeploy 提供的可視化函數進行可視化。
import matplotlib.pyplot as plt
vis_im = fd.vision.visualize.vis_segmentation(im, result, 0.5)
plt.imshow(cv2.cvtColor(vis_im, cv2.COLOR_BGR2RGB))
接下來判斷鋼筋是否超限,爲了便於演示,兼容上面的判斷接口。此處將結果導出爲mask圖片。
import numpy as np
mask = np.reshape(result.label_map, result.shape)
mask = np.uint8(mask)
mask_path = "/home/aistudio/work/PaddleSeg/output/infer_result/mask.png"
cv2.imwrite(mask_path, mask)
# print(mask_path)
largestcomponent(mask_path)
post_img_path = mask_path.replace(".png", "_postprocessed.png")
# print(post_img_path)
excesslimit(post_img_path, "right", 0.7)
# 可視化判斷結果
im_path = "/home/aistudio/work/PaddleSeg/output/infer_result/mask_fullpostprocessed.png"
im = cv2.imread(im_path)
plt.imshow(im)
結論
本項目演示瞭如何在實際工業場景下,使用 PaddleSeg 開發套件進行語義分割模型訓練,並使用 FastDeploy 進行部署應用,解決鋼筋長度超限的自動監控問題。結果證明,本技術方案切實可行,可實現端到端全自動的鋼筋長度超限監控,爲企業生產降本增效。希望本應用範例可以給各行業從業人員和開發者帶來有益的啓發。