コラム

製造業DXやAI・画像処理技術に関する基礎知識から、現場での実践的な活用ノウハウまで。
エンジニア視点での技術解説や、業界の最新トレンドを発信します。
TOP > コラム > ジャガイモで学ぶ!OpenCV外観検査入門③ ~動体検知・カウント~

ジャガイモで学ぶ!OpenCV外観検査入門③ ~動体検知・カウント~

はじめに

製造現場での検査対象は、いつも止まっているとは限りません。
コンベアの上を流れる製品、作業するスタッフ、搬送ロボット……。現場は常に「動いて」います。

これまでの連載では「色」「大きさ」という静止画の情報を見てきましたが、今回は「動き(トラッキング)」がテーマです。「今、ラインに何個流れているか?」「どこで滞留しているか?」といった情報は、1枚の画像だけでは分かりません。

第3回となる本記事では、Pythonの画像処理ライブラリ「OpenCV」を使って、動いているジャガイモを追いかけ、個数を数える仕組みを解説していきます。

この記事でわかること

  • コンピューターが物体を「追いかける(追跡)」仕組み
  • 製造業の現場で「動体検知」や「カウント」がどのように活用されるか

この記事はこんな方におすすめ

  • 生産数や在庫の自動カウントに課題を感じている現場管理者の方
  • 生産数や「歩留まり(良品率)」の正確な把握に課題を感じている現場管理者の方


OpenCVとは

OpenCV(Open Source Computer Vision Library)は、Intelが開発した画像処理・コンピュータビジョンのためのツールです。簡単に言えば、「コンピュータに『人間の目』のような機能を持たせるための、無料ツール」です。製造業の検査装置や、自動運転車のカメラ、顔認証システムなど、世界中の「画像認識」の現場で標準的に使われています。

この記事では「OpenCV」を使って、「画像処理で何ができるのか?」をジャガイモを通して体験しています。

過去の記事

人間とコンピュータの「動き」の捉え方の違い

人間は目で見て「あ、ジャガイモが右へ流れていった」と直感的に分かりますが、コンピュータにとっては動画も「パラパラ漫画(静止画の連続)」に過ぎません。

そこで、コンピュータは以下のステップで「動き」を理解します。

  1. 検出(フレーム1): ジャガイモを見つける(座標A)。
  2. 検出(フレーム2) 次の瞬間の画像でジャガイモを見つける(座標B)。
  3. 紐付け(トラッキング): 「座標Aと座標Bは近いから、同じジャガイモだろう」と判断し、追跡する。

この「前のフレームの自分」と「今の自分」を紐付ける作業が、トラッキングの正体です。

ジャガイモを「追跡」してカウントする

今回は、ベルトコンベア上を流れるジャガイモを想定します。 第2回で学んだ「輪郭抽出」を動画の各コマで行い、IDを振っていくと、以下のようなことができます。

ロジックの概要

  1. 物体検出: 各フレームでジャガイモの位置(中心点)を特定する。
  2. 特定: 前のフレームの位置と距離が近いものを同じジャガイモとして引き継ぐ。
  3. カウント: 新しいIDが出現したら「生産数 +1」とする。

画面中央の基準線を通過したジャガイモの個数だけ、左上のカウンターが増加します。
※上の画像では初期値10から2個通過しているため、12となります。

このように、単に「ある/なし」だけでなく、「個々の物体を識別して追いかける」ことが可能になります。

実際のコード(Python / OpenCV)

動作するサンプルコードのイメージです。

import cv2, numpy as np, datetime, os, math

def main():
    # 1. カメラ設定 (Camera Setup)
    # iPhoneをWebカメラとして使用し、見つからなければ終了
    cap = cv2.VideoCapture(0)
    if not cap.isOpened(): return print("Camera not found.")
    
    # 2. 録画準備 (Recording Setup)
    # 画面サイズを取得し、デスクトップに日時付きファイル名で保存準備
    w, h = int(cap.get(3)), int(cap.get(4))
    path = os.path.join(os.path.expanduser("~/Desktop"), f"potato_{datetime.datetime.now():%Y%m%d_%H%M%S}.mp4")
    out = cv2.VideoWriter(path, cv2.VideoWriter_fourcc(*'mp4v'), 30, (w, h))
    print(f"REC: {path} (Press 'q' to quit)")

    # 初期変数: 背景(avg), 追跡対象(tracked), カウント(count), ID(oid), カウントライン(line_y)
    avg, tracked, count, oid, line_y = None, {}, 0, 0, h // 2 + 50
    
    while cap.isOpened():
        ret, frame = cap.read()
        if not ret: break
        
        # 3. 画像処理 (Image Processing)
        # グレースケール化 -> ぼかし処理(ノイズ除去)
        gray = cv2.GaussianBlur(cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY), (21, 21), 0)
        # 最初のフレームを背景基準として保存
        if avg is None: avg = gray.copy().astype("float"); continue
        
        # 4. 動体検知 (Motion Detection)
        # 現在のフレームと背景の差分を計算し、二値化(白黒はっきりさせる)
        cv2.accumulateWeighted(gray, avg, 0.5)
        thresh = cv2.dilate(cv2.threshold(cv2.absdiff(gray, cv2.convertScaleAbs(avg)), 5, 255, cv2.THRESH_BINARY)[1], None, iterations=2)
        
        # 5. 輪郭抽出 & 追跡 (Tracking Logic)
        new_tracked, cur_cents = {}, []
        # 動いている部分の輪郭を見つける
        for c in cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[0]:
            if cv2.contourArea(c) < 5000: continue # 小さい動きは無視
            x, y, wa, ha = cv2.boundingRect(c)
            # 緑枠とラベルを描画
            cv2.rectangle(frame, (x, y), (x+wa, y+ha), (0, 255, 0), 2)
            cv2.putText(frame, "Potato", (x, y-10), 0, 0.5, (0, 255, 0), 2)
            cur_cents.append((x + wa//2, y + ha//2)) # 中心座標を保存

        # 既存の追跡オブジェクトと現在の位置をマッチング
        for cx, cy in cur_cents:
            match, min_d = None, 50
            for i, (ox, oy) in tracked.items():
                d = math.hypot(cx-ox, cy-oy)
                if d < min_d: min_d, match = d, i
            
            p_id = match if match is not None else oid
            if match is None: oid += 1
            
            # 6. カウント判定 (Counting)
            # ラインを上から下に通過したらカウントアップ
            if match is not None and tracked[p_id][1] < line_y <= cy:
                count += 1
                cv2.line(frame, (0, line_y), (w, line_y), (0, 255, 255), 5) # 通過時に黄色く光る

            new_tracked[p_id] = (cx, cy)
            del tracked[match] if match is not None else None
            
        tracked = new_tracked

        # 7. UI描画 & 保存 (Draw & Save)
        cv2.line(frame, (0, line_y), (w, line_y), (0, 0, 255), 2) # カウントライン(赤)
        cv2.putText(frame, f"Total: {count}", (10, 50), 0, 1, (0, 255, 255), 2)
        cv2.circle(frame, (w-30, 30), 10, (0, 0, 255), -1) # RECマーク
        cv2.putText(frame, "REC", (w-70, 35), 0, 0.5, (0, 0, 255), 2)
        
        out.write(frame); cv2.imshow("Potato Counter", frame)
        if cv2.waitKey(1) & 0xFF == ord('q'): break

    # 終了処理
    cap.release(); out.release(); cv2.destroyAllWindows()
    print(f"Done. Count: {count}")

if __name__ == "__main__": main()

製造現場への応用:トラッキングは「工場の目」と同じ

今回実践した「動くものを識別して追いかける」という技術は、実際の製造現場でも全く同じロジックで活用されています。生産管理や歩留まり改善の基礎となります。

業界検査の種類現場での検知対象
食品・日用品生産数カウントコンベアを流れる製品や、梱包された段ボールの総数カウント。(生産進捗の把握)
自動車・組立タクトタイム計測部品が工程を通過するのにかかった時間。(設備の速度低下や作業遅れの検知)
電子部品歩留まり・仕分け検査機通過後の「良品レーン」「排出レーン」それぞれへの移動数。(直行率・歩留まりの算出)

現場導入の「壁」を感じていませんか?

ここまでOpenCVによるトラッキングの基礎をお伝えしてきましたが、いざ実際の現場に導入しようとすると、高い「壁」にぶつかることがあります。

  • 対象物の重なり
    製品同士が重なったりすれ違ったりすると、IDが入れ替わったり、一つに見えてしまったりする。
  • 形状の変化(転がり・回転)
    コンベア上で製品が転がって「見え方(輪郭)」が変わると、コンピュータは「新しい物体が来た」と勘違いし、二重カウントしてしまう。
  • 背景ノイズや異物の誤検知
    OpenCVは「動くもの」を単純に検知します。そのため、コンベアの汚れ、虫、あるいは調整で入った作業者の手なども「製品」として誤ってカウントしてしまう。

こうした「重なり」や「複雑な動作解析」には、OpenCVのルールベース手法だけでは限界があります。そこで有効なのが、物体検知モデルをはじめとしたAI(ディープラーニング)技術です。

ムクイルについて

株式会社ムクイルでは、お客さまの要件に合わせて、システムを柔軟にカスタマイズ可能です。画像処理や画像解析の導入をお考えの企業さまは、ぜひムクイルにご相談ください。システムの導入から運用まで、最先端の技術と豊富な実績を活かしてお客さまの課題をサポートいたします。

お問い合わせはこちらから

導入のご相談やデモのご依頼は、下記よりお気軽にお寄せください。

コンタクト

お問い合わせ

コンタクト