質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

89.23%

選択した領域内にボールが落ちたかを判断したい

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 880

snake207

score 13

 やりたいこと

動画からボールの軌道を抽出するプログラムと
動画上にマウスで領域の4隅を選択すると線が引かれて領域を囲む
プログラムを作成した。
この2つのプログラムを組み合わせ、領域を選択してからボールの軌道を抽出する
プログラムを作成したい。

 発生したエラー

Traceback (most recent call last):
File "test.py",line 141, in <module>
plist.run()
File "test.py",line 100, in run
color_diff = cv2.absdiff(self.frame_next, self.frame_pre)   #フレーム間の差分
AttributeError: 'PointList' object has no attribute 'frame_next' 

# -*- coding: utf-8 -*-
import cv2
import sys
import numpy as np


VIDEO_DATE = "/home/pi/テニスコート撮影/tennis.AVI"
WINDOW_NAME = "MouseEvent"

def dilation(dilationSize, kernelSize, img):  # 膨張した画像にして返す
    kernel = np.ones((kernelSize, kernelSize), np.uint8)
    element = cv2.getStructuringElement(
        cv2.MORPH_RECT, (2 * dilationSize + 1, 2 * dilationSize + 1), (dilationSize, dilationSize))
    dilation_img = cv2.dilate(img, kernel, element)
    return dilation_img


def detect(gray_diff, thresh_diff=95, dilationSize=9, kernelSize=20):  # 一定面積以上の物体を検出
    retval, black_diff = cv2.threshold(
        gray_diff, thresh_diff, 255, cv2.THRESH_BINARY)  # 2値化
    dilation_img = dilation(dilationSize, kernelSize, black_diff)  # 膨張処理
    img = dilation_img.copy()
    image, contours, hierarchy = cv2.findContours(
        dilation_img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)  # 境界線検出

    ball_pos = []

    for i in range(len(contours)):  # 重心位置を計算
        count = len(contours[i])
        area = cv2.contourArea(contours[i])  # 面積計算
        x, y = 0.0, 0.0
        for j in range(count):
            x += contours[i][j][0][0]
            y += contours[i][j][0][1]

        x /= count
        y /= count
        x = int(x)
        y = int(y)
        ball_pos.append([x, y])

    return ball_pos, img


def displayCircle(image, ballList, thickness=5):
    for i in range(len(ballList)):
        x = int(ballList[i][0])
        y = int(ballList[i][1])
        cv2.circle(image, (x, y), 10, (0, 0, 255), thickness)
    return image


def resizeImage(image, w=2, h=2):
    height = image.shape[0]
    width = image.shape[1]
    resizedImage = cv2.resize(image, (int(width / w), int(height / h)))
    return resizedImage


def blackToColor(bImage):
    colorImage = np.array((bImage, bImage, bImage))
    colorImage = colorImage.transpose(1, 2, 0)
    return colorImage

class PointList():
    def __init__(self, npoints):
        self.video = cv2.VideoCapture(VIDEO_DATE)
        self.frame = None
        self.npoints = npoints
        self.ptlist = np.empty((npoints, 2), dtype=int)
        self.pos = 0
        cv2.setMouseCallback(WINDOW_NAME, self.onMouse)

    def add(self, x, y):
        if self.pos < self.npoints:
            self.ptlist[self.pos, :] = [x, y]
            self.pos += 1
            return True
        return False

    def run(self):
        while(self.video.isOpened()):
            end_flag, self.frame = self.video.read()
            if not end_flag:  # EOF
                break
            self.frame_pre = self.frame.copy()

            if(self.pos == self.npoints):
                # コメントアウト
                #print(self.ptlist)
                cv2.line(self.frame, (self.ptlist[0][0], self.ptlist[0][1]),
                     (self.ptlist[1][0], self.ptlist[1][1]), (0, 255, 0), 3)
                cv2.line(self.frame, (self.ptlist[1][0], self.ptlist[1][1]),
                     (self.ptlist[2][0], self.ptlist[2][1]), (0, 255, 0), 3)
                cv2.line(self.frame, (self.ptlist[2][0], self.ptlist[2][1]),
                     (self.ptlist[3][0], self.ptlist[3][1]), (0, 255, 0), 3)
                cv2.line(self.frame, (self.ptlist[3][0], self.ptlist[3][1]),
                     (self.ptlist[0][0], self.ptlist[0][1]), (0, 255, 0), 3)

            color_diff = cv2.absdiff(self.frame_next, self.frame_pre)  # フレーム間の差分計算
            gray_diff = cv2.cvtColor(color_diff, cv2.COLOR_BGR2GRAY)  # グレースケール変換
            retval, black_diff = cv2.threshold(gray_diff,30, 255, cv2.THRESH_BINARY)

            ball, dilation_img = detect(gray_diff)

            self.frame = displayCircle(self.frame, ball, 2)  # 丸で加工
            cImage = blackToColor(dilation_img)  # 2値化画像をカラーの配列サイズと同じにする
            im1 = resizeImage(frame, 2, 2)
            im2 = resizeImage(cImage, 2, 2)
            im_h = cv2.hconcat([im1, im2])  # 画像を横方向に連結

            cv2.imshow("Tracking", im_h)  # フレームを画面表示
            out.write(im_h)

            self.frame_pre = self.frame_next.copy()  # 次のフレームの読み込み

            cv2.imshow(WINDOW_NAME, self.frame)
            if cv2.waitKey(10) ==27: # Escキーで抜ける
                break

    def onMouse(self,event, x, y, flag, params):
        if event == cv2.EVENT_MOUSEMOVE:  # マウスが移動したときにx線とy線を更新する
            self.frame2 = np.copy(self.frame)
            h, w = self.frame2.shape[0], self.frame2.shape[1]
            cv2.line(self.frame2, (x, 0), (x, h - 1), (255, 0, 0))
            cv2.line(self.frame2, (0, y), (w - 1, y), (255, 0, 0))
            cv2.imshow(WINDOW_NAME, self.frame2)

        if event == cv2.EVENT_LBUTTONDOWN:  # レフトボタンをクリックしたとき、ptlist配列にx,y座標を格納する
            if self.add(x, y):
                print('[%d] ( %d, %d )' % (ptlist.pos - 1, x, y))
                cv2.circle(self.frame, (x, y), 3, (0, 0, 255), 3)
                cv2.imshow(WINDOW_NAME, self.frame)
            else:
                print('All points have selected.  Press ESC-key.')

if __name__ == '__main__':
    cv2.namedWindow(WINDOW_NAME)
    npoints = 4
    ptlist = PointList(npoints)
    ptlist.run()

 質問内容

・エラーの原因は何なのか?
・元の2つのプログラムを組み合わせるにはどうしたらいいか?

下記にプログラム作成に参考したプログラムとサイトページのリンクを載せます。
回答の参考にしてください。

 マウスで領域選択(元のプログラム)

リンク内容

 ボール軌道抽出(元のプログラム)

import cv2
import sys
import numpy as np


def dilation(dilationSize, kernelSize, img):  # 膨張した画像にして返す
    kernel = np.ones((kernelSize, kernelSize), np.uint8)
    element = cv2.getStructuringElement(
        cv2.MORPH_RECT, (2 * dilationSize + 1, 2 * dilationSize + 1), (dilationSize, dilationSize))
    dilation_img = cv2.dilate(img, kernel, element)
    return dilation_img


def detect(gray_diff, thresh_diff=30, dilationSize=9, kernelSize=20):  # 一定面積以上の物体を検出
    retval, black_diff = cv2.threshold(
        gray_diff, thresh_diff, 255, cv2.THRESH_BINARY)  # 2値化
    dilation_img = dilation(dilationSize, kernelSize, black_diff)  # 膨張処理
    img = dilation_img.copy()
    image, contours, hierarchy = cv2.findContours(
        dilation_img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)  # 境界線検出

    ball_pos = []

    for i in range(len(contours)):  # 重心位置を計算
        count = len(contours[i])
        area = cv2.contourArea(contours[i])  # 面積計算
        x, y = 0.0, 0.0
        for j in range(count):
            x += contours[i][j][0][0]
            y += contours[i][j][0][1]

        x /= count
        y /= count
        x = int(x)
        y = int(y)
        ball_pos.append([x, y])

    return ball_pos, img


def displayCircle(image, ballList, thickness=5):
    for i in range(len(ballList)):
        x = int(ballList[i][0])
        y = int(ballList[i][1])
        cv2.circle(image, (x, y), 10, (0, 0, 255), thickness)
    return image


def resizeImage(image, w=2, h=2):
    height = image.shape[0]
    width = image.shape[1]
    resizedImage = cv2.resize(image, (int(width / w), int(height / h)))
    return resizedImage


def blackToColor(bImage):
    colorImage = np.array((bImage, bImage, bImage))
    colorImage = colorImage.transpose(1, 2, 0)
    return colorImage


def run(input_video_path, output_video_path):
    video = cv2.VideoCapture(input_video_path)  # videoファイルを読み込む
    # fourcc = cv2.VideoWriter_fourcc(*'MJPG')
    fourcc = cv2.VideoWriter_fourcc(*'XVID')

    if not video.isOpened():  # ファイルがオープンできない場合の処理.
        print("Could not open video")
        sys.exit()

    vidw = video.get(cv2.CAP_PROP_FRAME_WIDTH)
    vidh = video.get(cv2.CAP_PROP_FRAME_HEIGHT)
    out = cv2.VideoWriter(output_video_path, fourcc, 20.0,
                          (int(vidw), int(vidh)))  # 出力先のファイルを開く

    ok, frame = video.read()  # 最初のフレームを読み込む
    if not ok:
        print('Cannot read video file')
        sys.exit()

    frame_pre = frame.copy()

    while True:
        ok, frame = video.read()  # フレームを読み込む
        if not ok:
            break
        frame_next = frame.copy()

        color_diff = cv2.absdiff(frame_next, frame_pre)  # フレーム間の差分計算
        gray_diff = cv2.cvtColor(color_diff, cv2.COLOR_BGR2GRAY)  # グレースケール変換
        retval, black_diff = cv2.threshold(
            gray_diff, 30, 255, cv2.THRESH_BINARY)

        ball, dilation_img = detect(gray_diff)

        frame = displayCircle(frame, ball, 2)  # 丸で加工
        cImage = blackToColor(dilation_img)  # 2値化画像をカラーの配列サイズと同じにする
        im1 = resizeImage(frame, 2, 2)
        im2 = resizeImage(cImage, 2, 2)
        im_h = cv2.hconcat([im1, im2])  # 画像を横方向に連結

        cv2.imshow("Tracking", im_h)  # フレームを画面表示
        out.write(im_h)

        frame_pre = frame_next.copy()  # 次のフレームの読み込み

        k = cv2.waitKey(1) & 0xff  # ESCを押したら中止
        if k == 27:
            break

    video.release()
    out.release()
    cv2.destroyAllWindows()


if __name__ == '__main__':
    inputFile="tennis.avi"
    outputFile="output.mp4"
    run(inputFile, outputFile)
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • hayataka2049

    2018/09/08 14:19

    まとめたプログラムを掲載し、できる範囲で自分でデバッグしてみた結果わかったことも書いてください

    キャンセル

  • umyu

    2018/09/08 16:32 編集

    まとめたプログラムは定義はありますが、PointList#runの呼び出し部分がありません。転記漏れでしょうか?

    キャンセル

  • snake207

    2018/09/08 16:46 編集

    すいません、追加しときます。

    キャンセル

回答 1

checkベストアンサー

+2

 エラーの原因について

AttributeError: 'PointList' object has no attribute 'frame_next' 

PointList オブジェクトの frame_next という attribute を 100行目で参照しようとしたけど、そんなのないよというエラーです。

この時点で self.frame_next は定義されていない。
どこかから持ってきたコードだとしたら、移植が不十分になっています。

color_diff = cv2.absdiff(self.frame_next, self.frame_pre)

 やりたいことに関して

  • 動画からボールの軌道を抽出するプログラム
  • 動画上にマウスで領域の4隅を選択すると線が引かれて領域を囲むプログラム

ソースを見ました。意図として

  1. フレーム間背景差分をとる。
  2. 差分があった (つまり、動いたもの) はボールと判定して、検出する。
  3. 検出対象の領域は、マウスで選択した矩形内にしたい

ということですね。
とりあえず、一度にやろうとするのではなく、以下のように1つずつ完成させて繋ぎこむほうがよいと思います。
一度にやろうとすると、どこかで問題があったときなにが原因かわかりづらくなるので。

  • フレーム間背景差分を表示するプログラム
  • マウスで選択した4隅から矩形を設定するプログラム
  • 背景差分の結果からボールを検出するプログラム

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/09/15 16:32 編集

    回答、ありがとうございます。
    アドバイス頂いたように、一つずつ
    作成していこうと思います。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 89.23%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる