型抜きクッキーの効率化を機械学習で出来るかな?②

M10i
2025-04-22
2025-04-22

ハイ。M10iです。

機械学習を使って型抜きクッキーの効率化
の続きです。前回の予告どおり

~~~どうにかサイズを取得してみよう!~~~

aws-Rekognitionでサイズ・形が取れなかったんですよね。前回の失敗は
このテーマをTwo-Dimensional Bin Packing Problem(2Dパッキング問題=四角形を並べる)と判断した結果ですね。
実際にはこれは Irregular Bin Packing=不規則な形状のパッキングに該当すると思います。

そもそも☆という凹凸のあるクッキー型を機械学習で学習できる状態にしなければいけません。
ふわっとしたchatGTPの答えでいける!と思わずに考えましょう。自戒。

今回の主題、☆を機械学習のデータとして扱う方法について考えていきましょう。

まず、データの変換を考えます。M10iの浅い理解の範疇で2種類の方法があります。


①ポリゴン化

データをの頂点を座標の配列で持ちます。ざっくりですが機械学習に向いてます。
クッキー型なので必ず頂点が結ばれて面ができます。
連続した位置情報なので相対的な距離で示せる=回転できます。
cookieAI_2_1

②グリッド化(2Dマップ画像)

形を 白(1)/ 空きスペースを黒(0) の画像として表現。
単純ですが回転させるなどに弱い。
cookieAI_2_2
今回は効率化を考えると回転させたいので、①で進めたいと思います!

では前回のコードをちょっといじって、さっとデータを作ります。

OpenCV(cv2)という画像処理・画像解析のためのライブラリを使ってサイズを取得します。
aws-Rekognitionのカスタムラベルを生成するのも考えたんですが、サイズと形を取るためだけにはちょーっと贅沢が過ぎるかなって。人や動物とか認識できちゃうサービス使うなんてリッチが過ぎる、今回は線だけでいいのに・・・ってことでオープンソースOpenCVさんです。

ではさっそく。サイズが知りたいので3センチ角の正方形の型とその横に☆の抜型を配置します。

star_s画像から濃淡で境界線を拾って、塊を認識します。
正方形と☆を拾って、正方形と☆の比率で☆のサイズを計算します。
※画像のノイズが多いと線が抜けないのでコントラストはっきりした画像がおススメ

早速コード。前回の再利用なのでDjangoです。
htmlに表示させるためのコードが多いのでちょっと長いです。

views.py

import boto3
import cv2
import os
import numpy as np
import base64
import json
from django.shortcuts import render
from io import BytesIO
import matplotlib.pyplot as plt

s3_client = boto3.client('s3')
 
bucket_name ='bucket_name'
file_name='file_name'
 
def index(request):
    """ S3 から画像を取得し、Base64 で埋め込み & 形状データを取得 """
    # S3 から画像データを取得
    s3_response = s3_client.get_object(Bucket=bucket_name, Key=file_name)
    image_data = s3_response["Body"].read()

    # Base64 エンコードして HTML に埋め込む
    image_base64 = base64.b64encode(image_data).decode("utf-8")
    image_src = f"data:image/jpeg;base64,{image_base64}"

    # 形状の情報を取得
    square_size, star_size, star_real_size, img = extract_shapes(image_data)

    return render(request, "test/index.html", {
        "image_src": image_src,
        "square_size": square_size,
        "star_size": star_size,
        "star_real_size": star_real_size,
        "image_base64": img
    })
 
def extract_shapes(image_data):
    """S3から取得した画像の基準正方形と星形を検出し、サイズを算出"""
    np_img = np.frombuffer(image_data, np.uint8)
    image = cv2.imdecode(np_img, cv2.IMREAD_GRAYSCALE)

    # ===  すべての輪郭を取得   ===
    contours, hierarchy = cv2.findContours(
        thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
 
  # === 不要な要素が取れてるけど5番目が☆ ===
    star_contours = contours[4]
    # ===  基準の正方形と星形のバウンディングボックスを取得 ===
    x1, y1, w1, h1 = cv2.boundingRect(contours[0])  # 基準の正方形(左側)
    x2, y2, w2, h2 = cv2.boundingRect(star_contours)  # 星形(右側)

    # **ピクセル比率(1cmあたりのピクセル数)**
    cm_per_pixel = 3.0 / w1  # 3cm ÷ 基準正方形のピクセル幅

    # 星形の実寸サイズ
    star_width_cm = w2 * cm_per_pixel
    star_height_cm = h2 * cm_per_pixel

    # ピクセル → cm のスケール変換
    pixel_to_cm = 3.0 / w1  # 基準正方形の w1 を3cmと仮定

    #  輪郭の単純化(Douglas-Peucker アルゴリズム)
 # 元の輪郭長の1%を誤差として許容
    epsilon = 0.01 * cv2.arcLength(star_contours, True
    simplified_contour = cv2.approxPolyDP(star_contours, epsilon, True)

    #  簡約後のポリゴン座標を取得
    polygon_simplified = simplified_contour.reshape(-1, 2)
   
 # ピクセル → cm に変換(サイズ確認用)
    polygon_simplified_cm = [[round(
        x * pixel_to_cm, 3), round(y * pixel_to_cm, 3)] for x, y in polygon_simplified]

    # JSON データ作成
    polygon_data = {
        "reference_square": {
            "size_cm": 3.0,
            "bounding_box": [[round(x1 * pixel_to_cm, 3), round(y1 * pixel_to_cm, 3)],
                             [round((x1 + w1) * pixel_to_cm, 3), round((y1 + h1) * pixel_to_cm, 3)]]
        },
        "polygon": polygon_simplified .astype(int).tolist()
    }
    # JSON 保存
    json_filename = "polygon_data.json"
    with open(json_filename, "w") as f:
        json.dump(polygon_data, f, indent=4)

    # ポリゴンの頂点を取得
    x, y = zip(*polygon_simplified_cm)  # x座標, y座標を分離

    # 取得した型の画像を表示用に生成
    plt.figure(figsize=(5, 5))
    plt.plot(x + (x[0],), y + (y[0],), marker='o', linestyle='-')  # 閉じた形状にする
    plt.xlim(0, 10)
    plt.ylim(0, 10)
    plt.gca().invert_yaxis()  # 画像座標系に合わせる
    buffer = BytesIO()
    plt.savefig(buffer, format="png")
    buffer.seek(0)
    image_base64 = base64.b64encode(buffer.getvalue()).decode("utf-8")
 
    return (w1, h1), (w2, h2), (star_width_cm, star_height_cm), image_base64

index.html

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>クッキー型のサイズ測定</title>
    <style>
        body {
           background-color: #f0f0f0;
        }
        table {
            border-collapse: collapse;
            width: 50%;
        }
        th, td {
            border: 1px solid black;
            padding: 2px;
            text-align: center;
            font-size: 1em;
        }
       
    </style>
</head>
<body>
    <h2>クッキー型のサイズ測定</h2>

    <h3>画像</h3>
    <img src="" alt="クッキー型"  style="max-width: 250px;max-height: 250px; border: 1px solid blue;">

    <h3>検出されたサイズ</h3>
    <table>
        <tr>
            <th>オブジェクト</th>
            <th>ピクセルサイズ (W × H)</th>
            <th>実寸サイズ (cm)</th>
        </tr>
        <tr>
            <td>基準の正方形</td>
            <td> × px</td>
            <td>3.0 × 3.0 cm(基準)</td>
        </tr>
        <tr>
            <td>星形</td>
            <td> × px</td>
            <td> × cm</td>
        </tr>
    </table>
    <h3>認識した型</h3>
    <img src="data:image/png;base64," alt="Polygon Image" style="max-width: 250px;max-height: 250px;">
</body>
</html>

ではでは早速。
サイトにアクセスしてみます。
cookieAI_2_3
画像にノイズが多くて苦労しましたが!!!!!(あんまり関係ないので省略)
なんと画像から☆の形がポリゴンとして抜き出せましたよ!
ちょーっと微妙な形ですが、これはもう元の写真のクオリティに依存するので・・・・

次回、ポリゴンデータを使ってどうやって機械学習させるのかをやっていきたいと思いますっ
え、機械学習してなくない?!

M10iでした。