Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
ARコンテンツ作成勉強会:ARライトセーバーを作りながら覚えるOpenCV
Search
TakashiYoshinaga
March 17, 2018
Technology
0
710
ARコンテンツ作成勉強会:ARライトセーバーを作りながら覚えるOpenCV
OpenCVを使った画像処理の基礎に関する勉強会資料
TakashiYoshinaga
March 17, 2018
Tweet
Share
More Decks by TakashiYoshinaga
See All by TakashiYoshinaga
Nreal Light / Air 開発入門ハンズオン
takashiyoshinaga
0
990
MediaPipeのハンドトラッキングで作るARライトセイバー
takashiyoshinaga
1
320
UnityとZapWorksで始めようWebAR開発
takashiyoshinaga
0
3.8k
Getting Started with WebAR for HoloLens2 and Meta Quest
takashiyoshinaga
0
2.3k
Getting Started with HoloSDK
takashiyoshinaga
0
260
Getting Started with Non-Programming AR Development with MRTK v2.4.0
takashiyoshinaga
0
2.3k
Getting Started With MRTK (for Beginner)
takashiyoshinaga
0
700
始めようWebAR/VR開発
takashiyoshinaga
1
910
Getting Started with Azure Kinect DK
takashiyoshinaga
1
5.6k
Other Decks in Technology
See All in Technology
Creating Awesome Change in SmartNews
martin_lover
1
260
Рекомендации с нуля: как мы в Lamoda превратили главную страницу в ключевую точку входа для персонализированного шоппинга. Данил Комаров, Data Scientist, Lamoda Tech
lamodatech
0
630
アセスメントで紐解く、10Xのデータマネジメントの軌跡
10xinc
1
400
さくらの夕べ Debianナイト - さくらのVPS編
dictoss
0
190
Lakeflow Connectのご紹介
databricksjapan
0
100
20250413_湘南kaggler会_音声認識で使うのってメルス・・・なんだっけ?
sugupoko
1
440
Devinで模索する AIファースト開発〜ゼロベースから始めるDevOpsの進化〜
potix2
PRO
7
3.1k
フロントエンドも盛り上げたい!フロントエンドCBとAmplifyの軌跡
mkdev10
2
260
はじめてのSDET / My first challenge as a SDET
bun913
1
230
ワールドカフェI /チューターを改良する / World Café I and Improving the Tutors
ks91
PRO
0
100
はてなの開発20年史と DevOpsの歩み / DevOpsDays Tokyo 2025 Keynote
daiksy
6
1.5k
PicoRabbit: a Tiny Presentation Device Powered by Ruby
harukasan
PRO
2
120
Featured
See All Featured
Raft: Consensus for Rubyists
vanstee
137
6.9k
Site-Speed That Sticks
csswizardry
5
480
CoffeeScript is Beautiful & I Never Want to Write Plain JavaScript Again
sstephenson
160
15k
Producing Creativity
orderedlist
PRO
344
40k
The Straight Up "How To Draw Better" Workshop
denniskardys
232
140k
Keith and Marios Guide to Fast Websites
keithpitt
411
22k
Building an army of robots
kneath
304
45k
Refactoring Trust on Your Teams (GOTO; Chicago 2020)
rmw
34
2.9k
A designer walks into a library…
pauljervisheath
205
24k
Designing Dashboards & Data Visualisations in Web Apps
destraynor
231
53k
[Rails World 2023 - Day 1 Closing Keynote] - The Magic of Rails
eileencodes
34
2.2k
Git: the NoSQL Database
bkeepers
PRO
430
65k
Transcript
ARコンテンツ作成勉強会 ARライトセーバーを作ろう #AR_Fukuoka
Instasaber 海外製のiPhoneアプリ。紙の認識にはDeepLearningを使用。
本日作成するコンテンツ 古典的な画像処理手法を用いて似たようなことをやってみよう
おおまかな手順 ① ② ③ ④
Visual Studioを起動
プロジェクトを作成 (1/2) ①File ②New ③Project
プロジェクトを作成 (2/2) ①VisualC# ②Windows Form Application ③OK
動作確認 Start
OpenCvSharpの導入 (1/4) ①Tools ②NuGet Package Manager ③Manage Nuget Package...
OpenCvSharpの導入 (2/4) ①Brows ②nuget.org
OpenCvSharpの導入 (3/4) ①OpenCVsharp3 ②OpenCvSharp3 AnyCPU ③パッケージのチェックをON ④Install
OpenCvSharpの導入 (4/4) NuGetを閉じる
スケールモードを設定 ①クリック ②AutoScaleModeをNoneに変更
画像表示領域を作成 (1/4) ①ToolBoxタブ ②PictureBox
画像表示領域を作成 (2/4) フォーム内をクリック
画像表示領域を作成 (3/4) ①PictureBoxをクリック ②Sizeを400,300 (なんでもOK)
画像表示領域を作成 (4/4) Formを選択してサイズを PictureBoxに合わせる Formのサイズはあとで変えるから適当でOK
Timerを用いた更新周期の設定 Timerを使うと任意に設定した間隔で処理(画像の取得・表示)を実行させられる ①ToolBoxタブ ②Timer
Timerを用いた更新周期の設定 ①Form上をクリック ②Timerが追加される ➂Intervalを30[ms] に変更(なんでもOK)
一定時間毎に呼び出される関数を作成 ①Timerをダブルクリック
一定時間毎に呼び出される関数を作成 timer1_Tick関数が追加される 一定時間沖にtimer1_Tick関数の内部に書いた処理が実行される(予定)
Formが表示されたらTimerをスタートさせる ①Form1[Design] ②Form1をクリック
Formが表示されたらTimerをスタートさせる ①⚡をクリック ②Shownをダブルクリック
Formが表示されたらTimerをスタートさせる namespace CVTest { public partial class Form1 : Form
{ public Form1() { InitializeComponent(); } private void timer1_Tick(object sender, EventArgs e) { } private void Form1_Shown(object sender, EventArgs e) { timer1.Start(); //タイマーの動作を開始 } } } ①Form1_Shownが追加される ②自分で追加
Webカメラの画像を取得 using OpenCvSharp; //OpenCvSharpの機能をインポート using OpenCvSharp.Extensions; namespace CVTest { public
partial class Form1 : Form { VideoCapture video = new VideoCapture(0); //Videoキャプチャ Mat bgr; //カラー画像を扱うクラス public Form1() { InitializeComponent(); } private void timer1_Tick(object sender, EventArgs e) { } /*****以下省略*****/
Webカメラの画像を取得 VideoCapture video = new VideoCapture(0); Mat bgr; public Form1()
{ InitializeComponent(); } private void timer1_Tick(object sender, EventArgs e) { bgr = video.RetrieveMat(); //カメラから画像を取得しbgrに記録 pictureBox1.BackgroundImage = bgr.ToBitmap(); bgr.Release(); //画像を記録したメモリを開放 } private void Form1_Shown(object sender, EventArgs e) { timer1.Start(); }
Webカメラの画像のサイズを調べる VideoCapture video = new VideoCapture(0); Mat bgr; public Form1()
{ InitializeComponent(); this.Text = video.FrameWidth + "," + video.FrameHeight; } private void timer1_Tick(object sender, EventArgs e) { bgr = video.RetrieveMat(); //カメラから画像を取得しbgrに記録 pictureBox1.BackgroundImage = bgr.ToBitmap(); bgr.Release(); //画像を記録したメモリを開放 } private void Form1_Shown(object sender, EventArgs e) { timer1.Start(); }
Webカメラの画像のサイズを変更 ①サイズを確認 ③PictureBoxをクリック ④サイズを変更 ②閉じる
ウィンドウサイズも変更
画像を左右反転させよう private void timer1_Tick(object sender, EventArgs e) { bgr =
video.RetrieveMat(); bgr = bgr.Flip(FlipMode.Y); pictureBox1.BackgroundImage = bgr.ToBitmap(); bgr.Release(); } x y Flip
画像処理の過程を表示させよう 縦横に広げる (1.5倍くらい)
画像処理の過程を表示させよう PictureBoxをコピペし、 サイズを半分くらいに pictureBox1 pictureBox2
PictureBoxのサイズに合わせた画像縮小設定 ①pictureBox2クリック pictureBox1 pictureBox2 ②BackgroundImageLayout をZoomに変更
過程表示用パネルをもう一個作成 pictureBox2をコピペ pictureBox1 pictureBox2 pictureBox3
画像処理用の画像を作成 VideoCapture video; Mat bgr; Mat img1; //画像処理に用いる画像を保持 public Form1(){
/*省略*/ } private void timer1_Tick(object sender, EventArgs e) { bgr = video.RetrieveMat(); bgr=bgr.Flip(FlipMode.Y); ImageProcessing(); pictureBox1.BackgroundImage = bgr.ToBitmap(); pictureBox2.BackgroundImage = img1.ToBitmap(); bgr.Release(); img1.Release(); } //以下の関数に画像処理のコードを書いていく private void ImageProcessing() { img1 = bgr.Clone(); }
動作確認
OpenCVに慣れよう:グレースケール変換 Mat bgr; Mat img1; //画像処理に用いる画像を保持 public Form1(){ /*省略*/ }
private void timer1_Tick(object sender, EventArgs e) { bgr = video.RetrieveMat(); bgr=bgr.Flip(FlipMode.Y); ImageProcessing(); pictureBox1.BackgroundImage = bgr.ToBitmap(); pictureBox2.BackgroundImage = img1.ToBitmap(); bgr.Release(); img1.Release(); } private void ImageProcessing() { img1 = bgr.CvtColor(ColorConversionCodes.BGR2GRAY); } CvtColorで画像変換
確認 この部分のみを追跡したい
グレースケール画像を二値化してみよう 0 255 0 255 0~255の輝度値を、ある値(しきい値)以上か未満かで分離することを二値化と呼ぶ。 画像処理すべき領域か否かをはっきり分けることができるため非常に重要なテクニック。
二値化 Mat img1; //画像処理に用いる画像を保持 private void timer1_Tick(object sender, EventArgs e)
{ bgr = video.RetrieveMat(); bgr=bgr.Flip(FlipMode.Y); ImageProcessing(); pictureBox1.BackgroundImage = bgr.ToBitmap(); pictureBox2.BackgroundImage = img1.ToBitmap(); bgr.Release(); img1.Release(); } private void ImageProcessing() { img1 = bgr.CvtColor(ColorConversionCodes.BGR2GRAY); img1 = img1.Threshold(128, 255, ThresholdTypes.Binary); } しきい値
動作確認
GUIでしきい値(Threshold)を変化させよう Trackbar
GUIでしきい値(Threshold)を変化させよう Trackbarを配置
GUIでしきい値(Threshold)を変化させよう ① Trackbarをクリック ③Maximumを255 Minimumを0 TrackBarで入力できる値を0~255に、初期値を128に設定 ② をクリック ④Valueを128
GUIでしきい値(Threshold)を変化させよう ①⚡をクリック ②Scrollをダブルクリック TrackBarをスクロールしたときの挙動を記述する関数を生成
GUIでしきい値(Threshold)を変化させよう Mat src; Mat dst; int th = 128; /*コンストラクタ省略*/
private void timer1_Tick(object sender, EventArgs e) { bgr = video.RetrieveMat(); bgr=bgr.Flip(FlipMode.Y); ImageProcessing(); pictureBox1.BackgroundImage = bgr.ToBitmap(); pictureBox2.BackgroundImage = img1.ToBitmap(); bgr.Release(); img1.Release(); } private void ImageProcessing() { img1 = bgr.CvtColor(ColorConversionCodes.BGR2GRAY); img1 = img1.Threshold(th, 255, ThresholdTypes.Binary); } private void trackBar1_Scroll(object sender, EventArgs e) { th = trackBar1.Value; } しきい値をthに変更 trackBar1_Scroll関数が追加される
現状確認 しきい値で結構分離できる 小さい白画素の塊が邪魔
Erode(収縮)を用いたノイズ除去 Mat img1; Mat img2; int th; /* 中略 */
private void ImageProcessing2() { img1 = bgr.CvtColor(ColorConversionCodes.BGR2GRAY); img1 = img1.Threshold(th, 255, ThresholdTypes.Binary); img2 = img1.Erode(new Mat(), null, 3); } 白画素の縮小を数回行う http://labs.eecs.tottori-u.ac.jp/sd/Member/oyamada/OpenCV/html/index.html 成功例 やり過ぎ例
ノイズ除去後の画像を表示 private void timer1_Tick(object sender, EventArgs e) { bgr =
video.RetrieveMat(); Cv2.Flip(bgr, bgr, FlipMode.Y); ImageProcessing(); pictureBox1.BackgroundImage = bgr.ToBitmap(); pictureBox2.BackgroundImage = img1.ToBitmap(); pictureBox3.BackgroundImage = img2.ToBitmap(); bgr.Release(); img1.Release(); img2.Release(); }
None
Dilate(膨張)を用いた復元 Mat img1; Mat img2; int th; /* 中略 */
private void ImageProcessing2() { img1 = bgr.CvtColor(ColorConversionCodes.BGR2GRAY); img1 = img1.Threshold(th, 255, ThresholdTypes.Binary); img2 = img1.Erode(new Mat(), null, 3); img2 = img2.Dilate(new Mat(), null, 4); } 白画素が小さすぎる場合は膨張させる Erode Dilate
休憩
輪郭の取得 private void ImageProcessing() { img1 = bgr.CvtColor(ColorConversionCodes.BGR2GRAY); img1 =
img1.Threshold(th, 255, ThresholdTypes.Binary); img2 = img1.Erode(new Mat(), null, 3); img2 = img2.Dilate(new Mat(), null, 3); OpenCvSharp.Point[][] contours; HierarchyIndex[] h; img2.FindContours( out contours, out h, RetrievalModes.External, ContourApproximationModes.ApproxSimple); }
輪郭の描画 private void ImageProcessing() { img1 = bgr.CvtColor(ColorConversionCodes.BGR2GRAY); img1 =
img1.Threshold(th, 255, ThresholdTypes.Binary); img2 = img1.Erode(new Mat(), null, 3); img2 = img2.Dilate(new Mat(), null, 3); OpenCvSharp.Point[][] contours; HierarchyIndex[] h; img2.FindContours( out contours, out h, RetrievalModes.External, ContourApproximationModes.ApproxSimple); } このあと、ここにコードを追加する
輪郭の描画 for (int i = 0; i < contours.Length; i++)
{ bgr.DrawContours(contours, i, Scalar.Cyan); }
最大面積かつ縦長な領域を求める(1/3) double maxArea = -1; OpenCvSharp.Point[] track = null; for
(int i = 0; i < contours.Length; i++){ bgr.DrawContours(contours, i, Scalar.Cyan); double area = Cv2.ContourArea(contours[i]); RotatedRect rect = Cv2.MinAreaRect(contours[i]); double aspect = rect.Size.Height / rect.Size.Width; if (aspect < 1){ aspect = 1 / aspect; } if (area > maxArea){ maxArea = area; track=contours[i]; } } if (area > maxArea && aspect>2.0){
最大面積かつ縦長な領域を求める(2/3) OpenCvSharp.Point[] track = null; RotatedRect maxRect = new RotatedRect();
for (int i = 0; i < contours.Length; i++){ bgr.DrawContours(contours, i, Scalar.Cyan); double area = Cv2.ContourArea(contours[i]); RotatedRect rect = Cv2.MinAreaRect(contours[i]); double aspect = rect.Size.Height / rect.Size.Width; if (aspect < 1){ aspect = 1 / aspect; } if (area > maxArea && aspect>2.0){ maxArea = area; track=contours[i]; maxRect=rect; } }
最大面積かつ縦長な領域を求める(3/3) double maxArea = -1; OpenCvSharp.Point[] track = null; RotatedRect
maxRect = new RoatedRect(); for (int i = 0; i < contours.Length; i++){ /*最大かつ縦長の領域探し(前頁参照)*/ } if(track!=null){ Point2f[] p = maxRect.Points(); //四隅の座標を用いて矩形を描画 Cv2.Line(bgr, p[0], p[1], Scalar.Red); Cv2.Line(bgr, p[1], p[2], Scalar.Red); Cv2.Line(bgr, p[2], p[3], Scalar.Red); Cv2.Line(bgr, p[3], p[0], Scalar.Red); }
None
楕円近似と傾きの算出 OpenCvSharp.Point[] track = null; for (int i = 0;
i < contours.Length; i++){ /*最大かつ縦長の領域探し(前頁参照)*/ } if(track!=null && track.Count()>4){ Point2f[] p = maxRect.Points(); //四隅の座標を用いて矩形を描画 Cv2.Line(bgr, p[0], p[1], Scalar.Red); Cv2.Line(bgr, p[1], p[2], Scalar.Red); Cv2.Line(bgr, p[2], p[3], Scalar.Red); Cv2.Line(bgr, p[3], p[0], Scalar.Red); RotatedRect ellipse = Cv2.FitEllipse(track); Cv2.Ellipse(bgr, ellipse, Scalar.Green); this.Text=ellipse.Angle.ToString(); } 解説
None
140度 60度 120度
楕円近似と傾きの修正と矩形の位置取得 float angle; float cx, cy; private void ImageProcessing() {
/*二値化→ノイズ除去→最大かつ縦長の領域探し(省略)*/ if(track!=null && track.Count()>4){ /*Cv2.Lineで矩形描画(省略)*/ RotatedRect ellipse = Cv2.FitEllipse(track); Cv2.Ellipse(bgr, ellipse, Scalar.Green); angle = ellipse.Angle; if (angle < 90){ angle -= 180; } this.Text=angle.ToString(); cx = maxRect.Center.X; cy = maxRect.Center.Y; } }
http://arfukuoka.lolipop.jp/ opencv/saber.png
プロジェクト名フォルダ>プロジェクト名フォルダ>bin>Debug/Releaseにsaber.pngを入れる
画像の描画 ①pictureBox1クリック ③Paintをダブルクリック ②⚡クリック
画像の描画 ①pictureBox1_Paintが追加される
描画しよう Bitmap saber = new Bitmap("saber.png"); private void pictureBox1_Paint(object sender,
PaintEventArgs e) { Graphics g = e.Graphics; g.TranslateTransform(cx, cy); g.RotateTransform(angle); int h = saber.Height; int w = saber.Width; g.DrawImage(saber, 0, 0, w, h); } private void pictureBox1_Paint(object sender, PaintEventArgs e) { Graphics g = e.Graphics; /*中略*/ //画像のデフォルト位置を横半分ずらす g.DrawImage(saber, -w/2, 0, w, h); }
完成
None