前提・実現したいこと
python3.8 opencv4.5.3.56を使っているプログラミング初心者です。
pythonでopencvを用いて黒板の角4点の座標を取得するプログラムを考えています。
現在は画像を表示し、マウスイベントによって4点の座標を取得するプログラムにしているのですが、5回以上クリックした際の配列格納に悪戦苦闘しています。ので、エッジ検出などユーザー非依存の取得方法を用いたプログラムにしたく質問させていただきました。
### 処理を行う画像の前提条件
- 画像上には黒板が1つのみ写っている。
- 黒板は全体が写っている。(黒板に物体が被っていない)が、教員が黒板に被っている状態の黒板を処理するものとする。
- 黒板は45°以上の角度をつけて撮影していない。
- 撮影する状況として、壁が黒板と似た色であり、黒板のふちは木製である。
- 黒板にあたる光の強弱は考慮しないものとする。
- 認識の難易度を上げてしまう何かはとりあえず考えないものとする。(最終的にはそこも対策したい)
- 画像上で「黒板」が占める割合は約75%以上とする。
###実際の写真(例)
実際に座標を取得しようとしている黒板は湾曲しています。
該当のソースコード
python
1import cv2 2import os 3 4 5all_xy = [] 6 7def save_frame(video_path, frame_num, result_path): 8 cap = cv2.VideoCapture(video_path) 9 10 if not cap.isOpened(): 11 return 12 13 os.makedirs(os.path.dirname(result_path), exist_ok=True) 14 15 cap.set(cv2.CAP_PROP_POS_FRAMES, frame_num) 16 17 ret, frame = cap.read() 18 19 if ret: 20 cv2.imwrite(result_path, frame) 21 22save_frame('C:\Users\Mayod\PycharmProjects\pythonProject\sotsuken\WIN_20211112_10_43_35_Pro.mp4', 1, 'C:\Users\Mayod\PycharmProjects\pythonProject\sotsuken\sample_1.jpg') 23 24 25 26img = cv2.imread('sample_1.jpg', 1) 27cv2.namedWindow('image') 28 29 30def coordinates(event,x,y, flags,param): 31 sample_xy = [] 32 count = 0 33 if event == cv2.EVENT_LBUTTONDOWN: 34 sample_xy.append(x) 35 sample_xy.append(y) 36 print(x,y) 37 38 if (len(all_xy) < 4): 39 all_xy.append(sample_xy) 40 elif (count == 0): 41 all_xy[count] = sample_xy 42 count += 1 43 elif (count == 1): 44 all_xy[count] = sample_xy 45 count += 1 46 elif (count == 2): 47 all_xy[count] = sample_xy 48 count += 1 49 else: 50 all_xy[count] = sample_xy 51 count = 0 52 53 54 55 56 57 58cv2.imshow('image', img) 59cv2.setMouseCallback('image', coordinates) 60cv2.waitKey(0) 61print(all_xy) 62cv2.destroyAllWindows() 63 64 65 66
> エッジ検出などユーザー非依存の取得方法を用いたプログラムにしたく質問させていただきました。
- マウスでポチポチクリックするのではロバスト性が低いよね、めんどくさいし。
- 画像処理的にバチっと黒板が検出できたら楽だよね。
- もっと精度/ロバスト性のいい方法があれば知りたいなぁ
であってますか?もしそうなら質問をそう修正ください。
もしもっと別の質問があるならそのように変更ください。
そうしないと「投げやりな質問 -1」とされてしまいます。
参考:精度は高いと思いますが多分ロバスト性は低いです。
https://stackoverflow.com/questions/66461085/opencv-find-blackboard-edges-on-video-and-images
> 精度/ロバスト性
といった話をする場合には,「どのような画像を相手にする想定なのか」という話が必要と思う.
- 黒板よりも手前に様々な物があってオクルージョンがどうの…とか,黒板全体が写っていない(見切れている)とかいう絵でもやりたいかどうか
- 黒板をほぼほぼ{真横から,真上から}みたいなすごい角度から見た絵でもやりたいとか
- 「黒板」とか言ってるけど実際の色は不明だったり(緑とは限らないよね).
- 黒板の一部にのみ激しく光が当たっておりそうでない部分は激しく暗い,といったような環境も考慮する必要があるとか(教室の黒板とかってわりとそうなるよね)
- 黒板に 認識処理の難易度を激しく上げるような何か が描かれている場合は? 例えば「超リアルな黒板の絵」が描かれているかもしれないが?
- 画像に黒板がいくつ写っているか?という前提条件は設けるのか? 設けないなら,1個も写ってない場合にも対応する必要ある?
- 画像上で「黒板」の像が占める広さに関する前提条件は設けるのか? 黒板がめちゃくちゃ遠くにあるとかいう絵でも処理できないとダメ?
- etc...
(↑とはまた別の話)
マウスでクリックするという作業を行うこと自体に問題が無い(:5回以上クリックした際にどうのこうの…という話さえ潰せばそれで実用できる)のであれば,
無理して「ユーザー非依存の取得方法」という方向に行かなくても良いんじゃないの? とも見えるけど,そこらへんはどんな感じなのでしょう?
ご指摘ありがとうございます。処理を行う画像の前提条件として、以下の通りに考えています。
・画像上には黒板が1つのみ写っている。
・黒板は全体が写っている。(黒板に物体が被っていない)が、教員が黒板に被っている状態の黒板を処理するものとする。
・黒板は45°以上の角度をつけて撮影していない。
・撮影する状況として、壁が黒板と似た色であり、黒板のふちは木製である。
・黒板にあたる光の強弱は考慮しないものとする。
・認識の難易度を上げてしまう何かはとりあえず考えないものとする。(最終的にはそこも対策したい)
・画像上で「黒板」が占める割合は約90%以上とする。
・黒板を撮影する上で、
また、マウスクリックによる座標取得の問題点として取得した座標を格納する際に、[[x1,y1]...[x4,y4]]のように二次元配列に格納するのですが、5回以上クリックしてしまうと、[x5,y5]...も格納されてしまいその後の処理に影響がでてしまいます。この問題はリセットするキーを作成することで、やり直しをするよう考えています。
返答遅れてしまい、本当に申し訳ないです。
実際の写真(例)を掲載できますか?
> 5回以上クリックしてしまうと(略)
5回目でリセットして「リストを空にしたからまた初めからやってくれ」もありかもしれませんね。
(入力や特例は少ない方がプログラミングしやすいですし)
クリックに関しては,「点を最大4個まで配置できるもの」をどう作るか? というだけの話ですよね.例えば,以下のようなものとかは簡単に作れて操作もしやすいような気がします.
[左クリックの機能]
・クリック位置に最も近い既存の点との距離が一定値以下の場合には,その点をクリック位置に移動させる.(:点の位置の修正機能)
・全ての既存点から一定以上離れている場合であって,且つ,既存の点の個数が4個に満たない場合には,クリック位置に新たな点を追加する.(:点を配置する機能)
・(上記のいずれにも該当しない場合は何もしない)
[右クリックの機能]
・クリック位置に最も近い既存の点との距離が一定値以下の場合には,その点を削除する.
・(上記に該当しない場合は何もしない)
認識処理に関しては,相手にする絵の条件(対象とする黒板と,その撮影状況とか)が具体的にあるならば,その条件をうまいこと利用した方法は無いだろうか…? と考えてみるとよいでしょう.
例えば,
黒板の面の色 も 縁の色 もわかっていて,黒板の縁の形状は(撮影画像上で)直線であると言える場合であれば…
面と縁の間の直線的な境界線(エッジ)を検出することは比較的容易と思える(色味や明るさを手掛かりにして頑張れそうである).
その直線のどちら側が黒板の面であるのかも判断がつくから,境界線を1本見つけるごとに「画像内でこの線よりもこっち側」という絞り込みができる→4本見つけたら面の範囲が分かるでは…!?
(4つ角の座標が欲しいなら直線の交点として求めればいい)
…とかなんとかいうアイデアが浮かぶ.
で,「でも面と縁の境界線ではない箇所から線を見つけてしまうことだって考えらるが,そういうのが多少あっても大丈夫にするにはどうするか…?」とか考えていく.
import cv2
import numpy as np
img = cv2.imread("test.jpeg")
# img = cv2.resize(img,None,fx=0.5,fy=0.5)
# cv2.imshow("img",img)
# cv2.waitKey(0)
# HSVでマスクを作る
img_HSV = cv2.cvtColor(img,cv2.COLOR_BGR2HSV)
img_bin = cv2.inRange(img,(100,128,180),(255,255,255))
# マスクをかぶせる
img_masked = img.copy()
img_masked[img_bin==0] = (0,0,0)
img_masked = cv2.resize(img_masked,None,fx=0.5,fy=0.5)
# cv2.imshow("img_masked",img_masked)
# cv2.waitKey(0)
img_g = cv2.cvtColor(img_masked,cv2.COLOR_BGR2GRAY)
# cv2.imshow("img_g",img_g)
みたいな感じで「人間が見れば線」は検出できましたが、Hough検出で線を出そうとすると、背景の灰色と黒板の淵の茶色が灰色っぽい感じでうまくいかなさそうな感じがします。「なら黒板の四角をblob検出でだせるじゃないか」もあるとは思いますが、前提の「人が写るかもよ?」を加味するとどこまでうまくいくか未知数です。
パラメータを駆使してこの画像ではうまくいくパラメータは出せるかもしれませんが、汎用性は相当低そうです。多分写真の左に窓があって、そこから光が差し込んで白っぽくなっているのでハードルが上がっているのだと思います。
という感じで、fanaさんのクリックのやり方が一番いいと思います。
黒板の四隅に派手な赤のマークを付ければすぐに検出できそうですが…
ありがとうございます。光の差し込み等も考慮するとクリックによる座標取得が良さそうです。他の解決方法として、赤色や比較的わかりやすい色(黒板と酷似しない)の磁石を4つ(または4つ以上)用意し、画像処理で座標取得も考えています。この方法が良ければこの質門は解決としたいと思っています。
> 赤色や比較的わかりやすい色(黒板と酷似しない)の磁石を4つ(または4つ以上)用意し、画像処理で座標取得も考えています。
これが抜群に簡単です。基本的なやり方は「2021/11/30 21:57」のコメントのやり方をベースに、赤色領域に対してblob検出と中心の計算でいけるはずです。
人工的なマーカ(この話だと磁石)を配置する場合,画像処理でN個の候補が見つかったときに
・マーカではないものが検出されている可能性がある
・本物のマーカは所望の個数検出されていない可能性がある
といったことを(どの程度頑張るべき話なのかは不明ですが)相応に考える必要はあるかもしれません.
・前者側について言えば,マーカが小さくて形状がシンプルであればあるほど誤検出の除去が難しくなるでしょう.
・人物がマーカに被る可能性があるならば画像処理の性能とは無関係に後者側が生じ得ます.
回答2件
あなたの回答
tips
プレビュー