物体検出モデルとマーカーによる位置検出で商品棚の品切れを検出してみました
1 はじめに
CX事業本部の平内(SIN)です。
以前、商品棚の品切れを検出する物体検出モデルを作成して見ました。
今回は、この物体検出モデルと、マーカーを組み合わせて、より精度を上げる方法を検討してみました。
最初に、動作している様子です。 前段は、マーカー併用で品切れを検出しているもので、後半は、物体検出モデルの推論結果も併せて表示しています。
2 判定要領
(1) 問題点
「縦長矩形の商品の背景」で学習したモデルは、品切れの場所を高い信頼度で検出できます。
しかし、品切れの商品が2つ並んだりすると、途端に検出できなくなってしまいます。
この時、採用する信頼度を下げて確認すると、低い信頼度で、それなりに検出していることが確認できました。
(2) マーカー併用
そこで、商品棚配置したマーカーを検出し、その相対位置で各商品のエリアを計算します。
そこに、信頼度の低い検出結果を組み合わせて、エリアの重複度から商品の品切を判定することにしました。
3 コード
動作しているコードです。
Amazon Sage Makerで作成した物体検出モデルは、MXNetでMac上で使用しています。
import cv2 import numpy as np import random import mxnet as mx from collections import namedtuple DEVICE_ID = 1 WIDTH = 800 HEIGHT = 600 FPS = 24 MODEL_PATH = './model/deploy_model_algo_1' class Model: def __init__(self): self.__SHAPE = 512 input_shapes=[('data', (1, 3, self.__SHAPE, self.__SHAPE))] self.__Batch = namedtuple('Batch', ['data']) sym, arg_params, aux_params = mx.model.load_checkpoint(MODEL_PATH, 0) self.__mod = mx.mod.Module(symbol=sym, label_names=[], context=mx.cpu()) self.__mod.bind(for_training=False, data_shapes=input_shapes) self.__mod.set_params(arg_params, aux_params) def inference(self, frame): # 入力インターフェースへの画像変換 img = cv2.resize(frame, (self.__SHAPE, self.__SHAPE)) # 600*600 -> 512*512 img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # BGR -> RGB img = img.transpose((2, 0, 1)) # 512,512,3 -> 3,512,512 img = img[np.newaxis, :] # 3,512,512 -> 1,3,512,512 # 推論 self.__mod.forward(self.__Batch([mx.nd.array(img)])) prob = self.__mod.get_outputs()[0].asnumpy() return np.squeeze(prob) class Products: def __init__(self): self.__maker_max = 8 self.__marker_index = [18, 17, 22, 16, 25, 14, 15, 10] self.__reference_points = [(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0),(0,0)] self.__product_area = [[0] * 4] * 15 self.__marker_detection = [False] * self.__maker_max self.__product_names = [ "亀田の柿の種", "でん六豆", "ポリッピー(スパイス)", "ポリッピー(しお味)", "ポリッピー(チョコ)", "PRIME", "PRETEL", "Noir", "CRANZ(ORANGE)", "CRANZ(RED)", "カントリーマム", "CHEDDER CHEESE", "OREO", "チョコメリゼ", "フライドポテト(じゃがバター味)" ] # マーカー検出結果 def maker_detected(self): count = 0 for detection in self.__marker_detection: if(detection): count += 1 if(self.__maker_max == count): return True return False # マーカー位置の更新 def refresh_reference_points(self, ids, corners): for i, index in enumerate(self.__marker_index): position = self.__get_marker_mean(ids, corners, index) if(position != None): self.__marker_detection[i] = True self.__reference_points[i] = position self.__refresh_product_area() # 商品位置の更新 def __refresh_product_area(self): for row in range(3): (x1, y1) = self.__reference_points[row*2 + 0] x1 += 40 (x2, y2) = self.__reference_points[row*2 + 1] x2 -= 40 (x3, y3) = self.__reference_points[row*2 + 2] x3 += 40 (x4, y4) = self.__reference_points[row*2 + 3] x4 -= 40 for col in range(5): self.__product_area[col + row*5] = [ int(x1 + (x2-x1)/5 * col), int(y1 + (y2-y1)/5 * col), int(x3 + (x4-x3)/5 * (col+1)), int(y3 + (y4-y3)/5 * (col+1)), ] # 商品の品切を判定して表示 def disp_soldout(self, frame, soldout): pos = [False] * 15 for b in soldout: for i in range(15): a = self.__product_area[i] multiplicity = self.__multiplicity(a, b) if(0.3 < multiplicity): pos[i] = True for i, p in enumerate(pos): if p: frame = cv2.rectangle(frame, (self.__product_area[i][0], self.__product_area[i][1]), (self.__product_area[i][2], self.__product_area[i][3]), (0,0,255), 3) print("{} が売り切れです。".format(self.__product_names[i])) return frame # 重複度の計算 def __multiplicity(self, a, b): (ax_mn, ay_mn) = (a[0], a[1]) (ax_mx, ay_mx) = (a[2], a[3]) (bx_mn, by_mn) = (b[0], b[1]) (bx_mx, by_mx) = (b[2], b[3]) a_area = (ax_mx - ax_mn + 1) * (ay_mx - ay_mn + 1) b_area = (bx_mx - bx_mn + 1) * (by_mx - by_mn + 1) abx_mn = max(ax_mn, bx_mn) aby_mn = max(ay_mn, by_mn) abx_mx = min(ax_mx, bx_mx) aby_mx = min(ay_mx, by_mx) w = max(0, abx_mx - abx_mn + 1) h = max(0, aby_mx - aby_mn + 1) intersect = w*h return intersect / (a_area + b_area - intersect) # インデックスを指定してマーカーの中心座標を取得する def __get_marker_mean(self, ids, corners, index): for i, id in enumerate(ids): # マーカーのインデックス検索 if(id[0] == index): v = np.mean(corners[i][0],axis=0) # マーカーの四隅の座標から中心の座標を取得する return [v[0], v[1]] return None def main(): # マーカー初期化 aruco = cv2.aruco dictionary = aruco.getPredefinedDictionary(aruco.DICT_4X4_50) # Webカメラ初期化 cap = cv2.VideoCapture(DEVICE_ID) cap.set(cv2.CAP_PROP_FPS, FPS) cap.set(cv2.CAP_PROP_FRAME_WIDTH, WIDTH) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, HEIGHT) # モデル初期化 model = Model() # 商品クラス初期化 products = Products() while True: print("-------------") _, frame = cap.read() # マーカ検出 corners, ids, _ = aruco.detectMarkers(frame, dictionary) if ids is None: continue # マーカーが全部検出できるまで、判定を行わない if(products.maker_detected()==False): print("detecting markers...") else: # 推論 prob = model.inference(frame) # 推論結果を画像の解像度に併せる soldout = [] for p in prob: index = int(p[0]) confidence = p[1] if(confidence > 0.2): # 信頼度 x1 = int(p[2] * WIDTH) y1 = int(p[3] * HEIGHT) x2 = int(p[4] * WIDTH) y2 = int(p[5] * HEIGHT) soldout.append([x1, y1, x2, y2]) # 推論結果表示 frame = cv2.rectangle(frame,(x1, y1), (x2, y2), (255, 0, 0), 1) frame = cv2.rectangle(frame,(x1, y1), (x1 + 50, y1-20), (255, 0, 0), -1) label = "{:.2f}".format(confidence) frame = cv2.putText(frame,label,(x1+2, y1-2), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255,255,255), 1, cv2.LINE_AA) # 商品の品切を判定して表示 frame = products.disp_soldout(frame, soldout) # マーカー位置を更新 products.refresh_reference_points(ids, corners) #マーカ描画 aruco.drawDetectedMarkers(frame, corners, ids, (255, 255, 100)) cv2.imshow('frame', frame) cv2.waitKey(1) cap.release() cv2.destroyAllWindows() main()
4 最後に
機械学習のモデルの精度を上げることには限界がありますが、検出場所で判断ができる要件の場合、マーカーの組み合わせが相性が良さそううです。
また、マーカーを基準にすると、カメラのキャリブレーションも簡単になるように思います。