AIを用いたエイムアシストツールの作り方(画面キャプチャのみ)

1. はじめに

本プロジェクトでは、以下のライブラリなどを利用します。

  • OpenCV
    画像処理および描画用ライブラリ

  • mss
    画面キャプチャを高速に行うためのライブラリ

  • ONNX Runtime
    ONNX形式の深層学習モデルによる推論を実施

  • NumPy
    数値計算ライブラリ

また、設定ファイル(config.ini)を用いてモデルパスやキャプチャサイズなどのパラメータを管理します。

2. 必要なライブラリと環境設定

まずは、以下のパッケージをインストールしてください。

pip install opencv-python mss onnxruntime numpy

次に、プロジェクトルートに config.ini を配置します。初回起動時に自動で作成する仕組みも用意していますが、内容は以下のようになります:

[Settings]
model_path = yolov5nu.onnx
capture_size = 320

3. コードの概要

以下のコードは、画面の中央部分をキャプチャし、ONNXモデルによる物体検出を行い、検出結果のバウンディングボックスと信頼度を描画するシンプルな実装例です。
※エイムアシスト用のマウス移動や発射処理は削除しています。

主な構成要素

  • plot_one_box
    バウンディングボックスとラベル(信頼度)を画像上に描画する関数

  • detect_targets
    キャプチャしたフレームに対してONNXモデルの推論を実行し、検出候補(バウンディングボックスと信頼度)を取得する関数

  • load_config
    設定ファイルからパラメータを読み込む関数

  • main
    画面キャプチャ、推論、描画をループ処理で実行するメイン関数

4. コード詳細と解説

以下に、画面検出のみを行うコード例と、それぞれの処理について解説します。

import os
import cv2
import time
import math
import numpy as np
import mss
import onnxruntime as ort
import configparser

# --- バウンディングボックス描画用関数 ---
def plot_one_box(x, img, color=(128, 0, 0), label=None, line_thickness=None):
    # x: [x1, y1, w, h]
    tl = line_thickness or round(0.002 * (img.shape[0] + img.shape[1]) / 2) + 1
    c1 = (int(x[0]), int(x[1]))
    c2 = (int(x[0] + x[2]), int(x[1] + x[3]))
    cv2.rectangle(img, c1, c2, color, thickness=tl, lineType=cv2.LINE_AA)
    if label:
        tf = max(tl - 1, 1)
        t_size = cv2.getTextSize(label, 0, fontScale=tl/3, thickness=tf)[0]
        c2_label = (c1[0] + t_size[0], c1[1] - t_size[1] - 3)
        cv2.rectangle(img, c1, c2_label, color, -1, cv2.LINE_AA)
        cv2.putText(img, label, (c1[0], c1[1] - 2), 0, tl/3, (225, 255, 255), thickness=tf, lineType=cv2.LINE_AA)

# --- ONNX推論用、物体検出処理 ---
def detect_targets(session, frame, capture_size):
    input_shape = session.get_inputs()[0].shape
    input_width = input_shape[3] if input_shape[3] is not None else capture_size
    input_height = input_shape[2] if input_shape[2] is not None else capture_size

    frame_resized = cv2.resize(frame, (input_width, input_height))
    frame_rgb = cv2.cvtColor(frame_resized, cv2.COLOR_BGR2RGB)
    frame_tensor = frame_rgb.astype(np.float32) / 255.0
    frame_tensor = np.transpose(frame_tensor, (2, 0, 1))
    frame_tensor = np.expand_dims(frame_tensor, axis=0)

    input_dtype = session.get_inputs()[0].type
    if input_dtype == 'tensor(float16)':
        frame_tensor = frame_tensor.astype(np.float16)

    input_name = session.get_inputs()[0].name
    outputs = session.run(None, {input_name: frame_tensor})
    out = outputs[0]

    boxes = []
    confidences = []
    scale_x = capture_size / input_width
    scale_y = capture_size / input_height

    # モデルの出力形式に応じた処理
    if out.shape[-1] == 85:
        for detection in out[0]:
            x1, y1, x2, y2, conf, *class_scores = detection
            class_scores = np.array(class_scores)
            class_id = int(np.argmax(class_scores))
            score = conf * class_scores[class_id]
            if score < 0.27:
                continue
            x1 = int(x1 * scale_x)
            y1 = int(y1 * scale_y)
            x2 = int(x2 * scale_x)
            y2 = int(y2 * scale_y)
            box = [x1, y1, x2 - x1, y2 - y1]
            boxes.append(box)
            confidences.append(score)
    else:
        detections = out.transpose(0, 2, 1)[0]
        for det in detections:
            cx, cy, w, h = det[0:4]
            scores = det[4:]
            conf = np.max(scores)
            classID = int(np.argmax(scores))
            if conf < 0.27 or classID != 0:
                continue
            x1 = cx - w/2
            y1 = cy - h/2
            x2 = cx + w/2
            y2 = cy + h/2
            x1 = int(x1 * scale_x)
            y1 = int(y1 * scale_y)
            x2 = int(x2 * scale_x)
            y2 = int(y2 * scale_y)
            box = [x1, y1, x2 - x1, y2 - y1]
            boxes.append(box)
            confidences.append(float(conf))
    return boxes, confidences

# --- 設定ファイル読み込み ---
def load_config(config_path="config.ini"):
    config = configparser.ConfigParser()
    if os.path.exists(config_path):
        config.read(config_path, encoding="utf-8")
    else:
        config['Settings'] = {
            'model_path': 'yolov5nu.onnx',
            'capture_size': '320'
        }
        with open(config_path, 'w', encoding="utf-8") as configfile:
            config.write(configfile)
    return config['Settings']

# --- メイン処理 ---
def main():
    config = load_config()
    model_path = config.get('model_path', 'yolov5nu.onnx')
    capture_size = config.getint('capture_size', 320)

    # ONNX Runtimeのセッション設定
    sess_options = ort.SessionOptions()
    sess_options.intra_op_num_threads = 2
    sess_options.inter_op_num_threads = 2
    sess_options.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_ALL
    providers = [
        ('DmlExecutionProvider', {'device_id': 0, 'tunable_op_enable': 1}),
        'CPUExecutionProvider'
    ]
    session = ort.InferenceSession(model_path, sess_options, providers=providers)

    # モニター情報の取得(画面全体の解像度)
    with mss.mss() as sct:
        monitor = sct.monitors[1]
        screen_width = monitor["width"]
        screen_height = monitor["height"]
    capture_x = (screen_width - capture_size) // 2
    capture_y = (screen_height - capture_size) // 2

    print("画面検出を開始します。終了する場合は 'q' キーを押してください。")
    while True:
        with mss.mss() as sct:
            monitor_region = {"top": capture_y, "left": capture_x, "width": capture_size, "height": capture_size}
            sct_img = sct.grab(monitor_region)
            frame = np.array(sct_img)
            frame = cv2.cvtColor(frame, cv2.COLOR_BGRA2BGR)

        boxes, confidences = detect_targets(session, frame, capture_size)

        # 各検出結果を描画
        for box, conf in zip(boxes, confidences):
            label = "Conf: {:.2f}".format(conf)
            plot_one_box(box, frame, color=(128, 0, 0), label=label)

        cv2.imshow("Screen Detection", frame)
        if cv2.waitKey(1) & 0xFF == ord('q'):
            break

    cv2.destroyAllWindows()

if __name__ == '__main__':
    main()

各処理のポイント

  • 画面キャプチャ
    mss を用いて画面の特定領域(ここでは画面中央の capture_size x capture_size の領域)をキャプチャし、NumPy 配列として扱います。

  • ONNXモデルによる推論
    キャプチャした画像をモデルの入力サイズにリサイズし、RGB変換・正規化・チャネル順の変更を行った後、ONNX Runtime で推論を実施します。
    出力の形式に応じた処理を行い、検出された物体のバウンディングボックスと信頼度をリストにまとめます。

  • 描画
    OpenCV を利用し、各検出結果に対してバウンディングボックスと信頼度ラベルを描画します。plot_one_box 関数で美しく描画する処理を実現しています。

  • ループ処理
    キャプチャ→推論→描画のループを回し、リアルタイムに検出結果を表示。q キーが押されるとループを抜け、ウィンドウを破棄します。


5. まとめ

本記事では、画面検出を行うシンプルなプログラムの作成方法をご紹介しました。実際にこのテンプレートを元に、さらなる改良(例えばオブジェクトに対してのエイムアシストを発生させる処理や、UIのカスタマイズなど)を施すことで、自分だけのツールに仕上げることが可能です。

いいなと思ったら応援しよう!