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

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

ただいまの
回答率

88.59%

物体が地面に落ちた位置(バウンド位置)を検出したい

解決済

回答 2

投稿 編集

  • 評価
  • クリップ 1
  • VIEW 1,549

snake207

score 13

 やりたいこと

検出した物体(ボール)が地面に落ちたことを検出したい

 質問内容

映像にラインを引き、検出した物体(ボール)がラインと重なったかどうかの
判断をしたいのですがどうすればいいですか?

 作成したプログラム

カメラからの映像を取得し、限定した範囲だけ物体検出するプログラムを作成しました。
このプログラムに付け加える形にしたいです。

import cv2
import sys
import numpy as np

xmin,xmax = 112, 483  # 左上,右下のX座標
ymin,ymax = 172, 408  # 左上,右下のy座標

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=100, 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,offset=(xmin,ymin))  # 境界線検出

    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


video = cv2.VideoCapture(0)  # 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)


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

frame_pre = frame.copy()
frame_pre[ymin:ymax,xmin:xmax]

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

    color_diff = cv2.absdiff(frame_next[ymin:ymax, xmin:xmax], frame_pre[ymin:ymax, xmin:xmax])  # フレーム間の差分計算
    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)  # 丸で加工

    cv2.imshow("Tracking", frame)  # フレームを画面表示
    cv2.imshow("a",color_diff)

    frame_pre = frame_next.copy()  # 次のフレームの読み込み
    frame_pre[ymin:ymax, xmin:xmax]

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

video.release()
cv2.destroyAllWindows()
  • 気になる質問をクリップする

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

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

  • tiitoi

    2018/10/29 16:31

    ボールの検出自体はうまくいくことは確認出来ているのでしょうか?

    キャンセル

  • snake207

    2018/10/29 16:45

    はい、ボールの検出自体はほぼうまくできています。

    キャンセル

回答 2

checkベストアンサー

0

 例えば、、、

ラインを細長い四角形で定義し、その中に検出したボールの中心が含まれるかどうかで判定するのはどうでしょうか。

点が四角形の中に含まれるかどうかは pointPolygonTest() を使うと、判断できます。

retval = cv.pointPolygonTest(contour, pt, measureDist)
  • contour: 輪郭の点の一覧。(N, 2) の numpy 配列
  • pt: (x, y) のタプル
  • measureDist: 端点までの距離を計算するかどうか

返り値は、ポリゴンの内側にある場合は正の値、境界線上にある場合は0、外側にある場合は負の値を返します。

 サンプルコード

rect は四角形の4点を表す (4, 2) の numpy 配列、point は点を表す (x, y) のタプルとしたとき、cv2.pointPolygonTest(rect, point, False) >= 0 の場合、その点は四角形の中に含まれると判断できます。

import cv2
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.patches import Polygon
from matplotlib.lines import Line2D

# 線と長方形を定義
p1 = (50, 50)
p2 = (200, 100)
rect = np.array([[170, 113], [167, 57], [317, 50], [319, 105]])

def within(point, rect):
    '''点 point が四角形 rect に含まれているかどうか
    '''
    return cv2.pointPolygonTest(rect, point, False) >= 0

# 描画する。
# ------------------------------------------------
fig, ax = plt.subplots()
ax.set_xlim([0, 500])
ax.set_ylim([0, 200])
ax.add_patch(Polygon(rect, fill=False))  # 長方形を描画する。
for i, point in enumerate([p1, p2]):
    ax.text(*point, 'line {}'.format(i), fontsize=12)    # テキストを描画する。

    if within(point, rect):
        plt.plot([point[0]], [point[1]], marker='o', markersize=3, color="blue")  # 点を描画する。
        print('rect contains point {}.'.format(i))
    else:
        plt.plot([point[0]], [point[1]], marker='o', markersize=3, color="red")  # 点を描画する。
        print('rect does not contains point {}.'.format(i))

plt.show()

イメージ説明

 重心位置の計算について

質問欄のコードで1つ気になった点ですが

    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])

この部分は、cv2.moments() でモーメントを計算することで以下のように重心を求められます。参考リンク

for cnt in contours:  # 重心位置を計算
    M = cv2.moments(cnt, False)
    x, y = int(M["m10"] / M["m00"]), int(M["m01"] / M["m00"])
    ball_pos.append([x, y])

 追記

タプルで表現された点が4点で構成される四角に含まれるかどうかは以下で判定すればよいかと思います。

import cv2

def within(point, rect):
    '''点 point が四角形 rect に含まれているかどうか
    '''
    return cv2.pointPolygonTest(rect, point, False) >= 0

point = (200, 100)
rect = np.array([[170, 113], [167, 57], [317, 50], [319, 105]])

print(within(point, rect))  # True

 追記

とりあえず、定義した四角の中にボールの中心が入っている場合は赤色、入っていない場合は青色になるように、以下の関数を変更してみました。
rect の4点の座標値はラインに合わせて変えてください。

def displayCircle(image, ballList, thickness=5):
    # ここはラインに合わせて変えてください。
    rect = np.array([[170, 113], [167, 57], [317, 50], [319, 105]])

    for i in range(len(ballList)):
        x = int(ballList[i][0])
        y = int(ballList[i][1])

        if cv2.pointPolygonTest(rect, (x, y), False) >= 0:
            # 線の中にボールの中心が入っている赤色で表示。
            cv2.circle(image, (x, y), 10, (0, 0, 255), thickness)
        else:
            # 線の中にボールの中心が入っていない場合、青色で表示。
            cv2.circle(image, (x, y), 10, (255, 0, 0), thickness)

    return image

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2018/10/31 17:25

    ball_posに入れられた複数の要素を一つ一つ渡さなければ
    ならないということですか?
    もしそうなると、ball_pos[0],ball_pos[1],ball,pos[2]を
    それぞれいれることになると思うのですが。。

    キャンセル

  • 2018/10/31 17:29 編集

    > ball_pos[0],ball_pos[1],ball_pos[2]を
    それぞれいれることになると思うのですが。。
    おっしゃる通りそのようにする必要があります。
    なので、cv2.pointPolygonTest(rect, ball_pos, False) では駄目です。

    とりあえず変更例を追記に書きました。
    displayCircle() でボールの丸を描画しているようなので、定義した四角に入っていれば赤、入っていなければ青色で丸を描画する例を書きました。rect は線に合わせて変えてください。

    キャンセル

  • 2018/10/31 17:33

    変更例を追記していただき、ありがとうございます。
    こちらを参考にプログラムを書き直したいと思います。

    キャンセル

0

1. 映像にラインを引き、
2. 検出した物体(ボール)が重なったら、
3. USB端子に繋いでいるスピーカーから音を鳴らすにはどうしたらいいですか?

3はググったらすぐ出てきそうなのでいいとして、1, 2 についてです。ボールの位置が検出できるのであれば、その速度差から地面(orその他何か)にぶつかったかどうか判断することができそうですが。いかがでしょう。

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2018/10/27 12:03

    急激に速度が変化したところを地面にぶつかったと判断するという事でしょうか?

    キャンセル

  • 2018/10/27 12:10

    そうです(画像処理の知識はあまりないので参考になるかわかりませんが…)。

    キャンセル

  • 2018/10/27 18:01

    一旦、その方向で考えてみようと思います。回答、ありがとうございました。

    キャンセル

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

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

関連した質問

同じタグがついた質問を見る