2017年5月21日日曜日

USBマイクロスコープとOpenCV/Python

USBマイクロスコープがAliExpressから届きました。
ゼロ点調整用に使えないかなあ、と
これをポチっていたのです。
アフィリンクありません。
ストア:Sun-Shine Home
microscopio Digital Microscope USB Endoscope endoscopio Magnifier Camera microscoop standaard microscop microskop mikroskop
AliExpressにしては、珍しく箱入り者です!
というか初めての印刷箱です。
いつもパーツばかりですからね。
箱を開けると
足は伸びませんが三脚も入っています。
お医者さんが喉を見る懐中電灯って感じ。
先端にLEDが4つ。
ケーブルの途中のボリュームで明るさが調整できます。
付属のminiDVDに「RsCap1.10.exe」ソフトが入っていました。

インストール不要でそのまま実行できます。
[Device]の所で内蔵カメラとUSBカメラの切り替えもできます。
これはNotePCの内蔵カメラの映像。
こちらが、このUSBマイクロスコープの映像。
被写体との距離で倍率(視野角)を合わせ、
お尻を回してFocus合わせる。という使い方。
標準カメラの視野角(遠方もOK)~200倍まで可能。

家の廊下。標準レンズ位の焦点距離かな。


スケールの拡大。これ10mmの「10」。

スケールの線を拡大。
線間隔0.5mm。 倍率はわかりません。


マイクロスコープで遠方にも焦点を合わせられるのは初めてです。

これいいです。
これに照準を付けることできれば、ばっちりです。

こんなビューワソフトを簡単に作れる時代になっているじゃないかと
色々探していると
「OpenCV」というのに出くわしました。
GitHubにもあります。

OpenCV.JP
「OpenCV入門【3.0対応】(1)
 「OpenCVとは? 最新3.0の新機能概要とモジュール構成
辺りを見ると
OpenCVには、主に次の機能を利用できるとのこと。 
 ・画像/動画ファイルの入出力、カメラキャプチャ
 ・カメラキャリブレーション(Calibration)
 ・特徴点抽出
 ・物体認識(Object recognition)
 ・フィルター処理
 ・オブジェクト追跡(Object Tracking)
などなど

公式サポート言語は、C++、Java、Python
おっ!この前、bCNCの時にインストールしたPythonだ。
Pythonは、スクリプト言語なので、できそうな気がしてきました。

「Python OpenCV」で検索すると
Python版OpenCV入門 がでてきました。
「プログラミング初心者の方にはPythonでOpenCVを始めることをお勧めします。
 (Python版に慣れればC++版に移行するのは簡単です)」
とあります。
Python版に決定!

Pythonは、bCNCの時にインストールしているので
NumPyってのをインストールに進みます。
Downloadして解凍して
コマンドラインで「python setup.py install」をする。
が!
Librariesが無いと沢山の警告が出ています。
ERRORが多すぎて手に負えません。

そこで
Python、OpenCVを手軽にインストールする方法まとめ
「標準のPythonを使うと、OpenCVを動かすのに別のライブラリ(Numpy)も
 入れる必要があるため少々面倒です。
 そこで、それらのライブラリが最初から付属しているPythonパッケージ
 (WinPython、Python[x,y]、Anacondaなど)を使うことで
 Python+OpenCVの導入が簡単になります。」
とあります。
「Anacondaを使う場合:最も更新頻度が高くて人気なPythonパッケージ」
ともあります。。
Pythonに NumPy, Spyderが付属しているそうです。
よし、これに乗り替えることにします!

で、
この「Anacondaのインストール手順」で進めます。
インストーラは、
本家サイトから下の方に行って
Python2.7の64bit版をDownloadします。
メールアドレスの入力を求めるボックスが出てきますが入れなくて大丈夫でした。
DownloadしたAnaconda2-4.3.1-Windows-x86_64.exeを実行。
途中、Installフォルダは心配なのでCルートに作りました。
ここは、両方にチェック(環境変数のPath設定など)
途中、Python 2.7.13の表示がでていました。
15分位でしょうかかなり時間がかかります。
bCNCの時のPythonと違って
モジュールがいっぱい入ったようです。

次に「AnacondaにOpenCVをインストール」を見ながら
OpenCVのサイトに行き
下の方の[SourceForge]をクリックするか
上の方の[Releases]から飛んで最新の[Win pack]でもいいです。

[Win pack]をクリックしてopencv-3.2.0-vc14.exe をDownload
opencv-3.2.0-vc14.exeを実行すると解凍します。
解凍先はどこでもいいです。
生成されたフォルダの内から
...build\python\2.7\x64フォルダの「cv2.pyd」というファイルを
C:\Anaconda2\Lib\site-packages内にコピー。
これでOpenCVのインストール作業は完了。
えらく簡単です!
これでAnacondaからOpenCVを使えるはずです。

すべてのアプリを見ると「A]の所に
色々アイコンができています。
パイソン、アナコンダ、スパイダー、蛇が2匹と蜘蛛が1匹!
色んな生き物がPC内に入りました。蛇は好きです。
また、使い方がわからないので色々探すと
Spyder上でスクリプトを書いで実行できるとありました。
起動に、ちと時間かかります。Spyderはこんな画面です。
左側にスクリプト書いて上ので実行します。
右上にHELPなど、右下にエラー情報などでてきます。
Python 2.7と3系では文法が異なるそうです。
手始めは、ここが分かり易いです。
今から始めるPython その2 Spyderを使う
Python チュートリアル 」とかも

OpenCVの公式ドキュメント
Getting Started with Videos
から一番上のサンプルをそのままはりつけ実行してみます。
***** *****
import numpy as np
import cv2

cap = cv2.VideoCapture(0)

while(True):
    # Capture frame-by-frame
    ret, frame = cap.read()

    # Our operations on the frame come here
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

    # Display the resulting frame
    cv2.imshow('frame',gray)
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

# When everything done, release the capture
cap.release()
cv2.destroyAllWindows()
**********

何と!あっさり動きました!
ウインドウが開き、内蔵カメラの映像が白黒で出る。
"q"キーでウインドウが閉じられます。
すばらしい!
これは、簡単にできそうです。
面白くなってきました。
CNC2418は当分お休みになりそう。

外部にUSBカメラをつけます。
cv2.VideoCapture(0) の「0」を「1」にして
⇒ cv2.VideoCapture(1)
あっさり外部USBカメラの映像がでてきました。
いいですね~!

USBカメラは顕微鏡なので上下左右逆転します。
探すと「Python OpenCVの基礎 flipで画像を反転」に
cv2.flip 命令というのがあります。
これでいいでしょう。
cv2.flip(frame0, -1)

上下左右逆さまになりました!

調子に乗って、照準にいきます!
たぶんオーバーレイの命令があるはずです。
Inkscapeでターゲットを描きpng形式で保存。
後でわかったことですが
GIMP2でキャンパスを640x480にしてエクスポートしたら
線にアンチエイリアスがかかっていると輪郭のオーバーレイがうまくいかないので
GIMP2でアンチエイリアスなしの1pixel幅線で作り直したりして 
結局、これになりました。
どうしてもスナイパーライフル調になってしまいます。
この照準をオーバーレイする方法を探します。
この調子だと一発命令がありそうです。
ここを参照するも、どうもわかりません。
「Overlay」をOpenCVのサイトでSearchしてもでてこない。
でたどり着いたのがこれ!
何と!どうやら動画と静止画を四則演算できる様子。
しかし、参考サイトのままにすると真っ黒になる。
私のmask画像が良くないのでしょう。
結局、数式を30分ほど色々いじって、一応できました。
OpenCVはバージョンにより記述が変わっている場合が多いようです。
**********
# -*- coding: utf-8 -*-
import numpy as np
import cv2

# オーバーレイ画像の読み込み。-1はαチャンネル付きの意味。
overlay_image = cv2.imread('TargetD_VGA.png', -1)
# オーバーレイ画像からαチャンネルを抜出し0から1までの値にする。3チャンネルにする。
mask = cv2.cvtColor(overlay_image[:,:,3], cv2.COLOR_GRAY2BGR)/255.0
# αチャンネルはもういらないので消してしまう。
overlay_image = overlay_image[:,:,:3] 

cap1 = cv2.VideoCapture(1)

while(True):
    # Capture frame-by-frame
    ret, frame1 = cap1.read()
    # USBカメラが接続されていないとエラーにする
    if not ret:
              print 'error?'
              break
   
    # 上下左右反転
    FlipXY1 = cv2.flip(frame1, -1)

# オーバーレイ画像の合成。
    # 1.0/255で明るさが変わる。128明るい。512暗い。
    overlay1 = FlipXY1 * (1.0/255 - mask ) + overlay_image * (128 - mask )
   
    # Display the resulting frame
    cv2.imshow('USB-CAM #1 Q:END',overlay1)

    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

# When everything done, release the capture
cap1.release()
cv2.destroyAllWindows()
**********
これをSpyder上で実行ボタンを押すと。
いいっ!。
被写体は、以前、PCB切削テストのもの。
スコープはまだ手持ちです。
拡大です。
これこれ!
スクリプトで動画演算なので動きが遅いかもと思いきや。
遅延はほとんど感じられません。
動きも滑らかで15fps以上はあると思われます。
タスクマネージャーでこのプロセスのCPUパワーみると、17~18%です。
Corei3 1.8GHz グラフィックは非力です。映像の演算しているからですね~。

CNC2418に取り付ける時は鏡筒の回転と上下(倍率)調整できるように
取付方法を検討する必要あります。
この「OpenCVの描画機能」に直線や円の描き方がありました。
画像上に直線、長方形、円、楕円、多角形、テキストの追加があります。

三角形の塗りつぶしがわかりません。
OpenCVのバージョンにより命令の書式が変わっているようです。
かなり探し回って、
「ピリ辛的備忘録」
OpenCVによる画像処理〜図形などの描画2〜
cv2.fillConvexPoly命令でいいことがわかりました。

で映像の演算を使わずに照準を描くようにしました。
**********
# -*- coding: utf-8 -*-
import numpy as np
import cv2
# カメラからキャプチャする。
# (1):USBカメラ、(0):内蔵カメラ
cap1 = cv2.VideoCapture(1)
while(True):
       # Capture frame-by-frame
       ret, frame1 = cap1.read()
       # USBカメラが接続されていないとエラーにする
       if not ret:
           print 'error?'
           break

       # LINE描画、線幅:1px
       cv2.line(frame1,(320,0),(320,480),(0,0,0),1)
       cv2.line(frame1,(0,240),(640,240),(0,0,0),1)
       # 最後 -1:塗りつぶし、1:輪郭のみ、cv2.LINE_AA:アンチエイリアス
       cv2.circle(frame1,(320,240), 230, (0,0,0), 1,cv2.LINE_AA)
       cv2.circle(frame1,(320,240), 100, (0,0,0), 1,cv2.LINE_AA)
       cv2.circle(frame1,(320,240),  50, (0,0,255), 1)
       cv2.circle(frame1,(320,240),  25, (255,0,0), 1)
       cv2.circle(frame1,(320,240),  10, (0,255,0), 1)
       # 描画する多角形の頂点座標を配列に格納。
       # 三角形を塗りつぶしている。
       pts1 = np.array([[91,235],[91,245],[220,240]], np.int32)
       cv2.fillConvexPoly(frame1,pts1,(0,0,0),cv2.LINE_AA)
       pts2 = np.array([[549,235],[549,245],[420,240]], np.int32)
       cv2.fillConvexPoly(frame1,pts2,(0,0,0),cv2.LINE_AA)   
       pts3 = np.array([[315,11],[325,11],[320,140]], np.int32)
       cv2.fillConvexPoly(frame1,pts3,(0,0,0),cv2.LINE_AA)   
       pts4 = np.array([[315,469],[325,469],[320,340]], np.int32)
       cv2.fillConvexPoly(frame1,pts4,(0,0,0),cv2.LINE_AA)
       # 上下左右反転
       FlipXY1 = cv2.flip(frame1, -1)
   
       # 名前を入れてみた。
       font = cv2.FONT_HERSHEY_COMPLEX_SMALL
       Text = 'Target Scope & Electronic Projection Cross Gauge'
       cv2.putText(FlipXY1,Text,(10,472), font, 1,(255,255,255),1,cv2.LINE_AA)
       # Display the resulting frame
       cv2.imshow('INTERNAL-CAM #0    "q":END',FlipXY1)
       # キー入力"q"で終了
       if cv2.waitKey(1) & 0xFF == ord('q'):
            break

# When everything done, release the capture
cap1.release()
cv2.destroyAllWindows()
**********

スコープ手持ちでユニバーサル基板で見てみます。
ちょうど照準の円が偶然いい感じになっています。
CPUパワーは、2.2~3.0%です。軽くなりました。
映像の演算がないので軽くなっています。

ついでにこんなの
100均Webカメラ2台でステレオマッチングやってみた
を見ていると、カメラ2つにできそう。
ソースをそのまま貼り付けでは、さすがに動きませんね~?

OpenCVの公式ドキュメントのサンプルを書き直すことにします。
内蔵カメラとUSBマイクロスコープの2つにしてみました。
for文でも使えば、スマートになるのでしょうが

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

# カメラからキャプチャする。
# (0):内蔵カメラ、(1):USBカメラ:
cap0 = cv2.VideoCapture(0)
cap1 = cv2.VideoCapture(1)

while(True):
    # Capture frame-by-frame
    ret, frame0 = cap0.read()
    ret, frame1 = cap1.read()
    # USBカメラが接続されていないとエラーにする
    if not ret:
           print 'error?'
           break


    # LINE描画、線幅:1px
    cv2.line(frame0,(320,0),(320,480),(0,0,0),1)
    cv2.line(frame0,(0,240),(640,240),(0,0,0),1)
    cv2.line(frame1,(320,0),(320,480),(0,0,0),1)
    cv2.line(frame1,(0,240),(640,240),(0,0,0),1)
    # 最後 -1:塗りつぶし、1:輪郭のみ、cv2.LINE_AA:アンチエイリアス
    cv2.circle(frame0,(320,240), 230, (0,0,0), 1,cv2.LINE_AA)
    cv2.circle(frame0,(320,240), 100, (0,0,0), 1,cv2.LINE_AA)
    cv2.circle(frame0,(320,240),  50, (0,0,255), 1)
    cv2.circle(frame0,(320,240),  25, (255,0,0), 1)
    cv2.circle(frame0,(320,240),  10, (0,255,0), 1)
    cv2.circle(frame1,(320,240), 230, (0,0,0), 1,cv2.LINE_AA)
    cv2.circle(frame1,(320,240), 100, (0,0,0), 1,cv2.LINE_AA)
    cv2.circle(frame1,(320,240),  50, (0,0,255), 1)
    cv2.circle(frame1,(320,240),  25, (255,0,0), 1)
    cv2.circle(frame1,(320,240),  10, (0,255,0), 1)

    # 描画する多角形の頂点座標を配列に格納。
    # 三角形を塗りつぶしている。
    pts1 = np.array([[91,235],[91,245],[220,240]], np.int32)
    cv2.fillConvexPoly(frame0,pts1,(0,0,0),cv2.LINE_AA)
    cv2.fillConvexPoly(frame1,pts1,(0,0,0),cv2.LINE_AA)
    pts2 = np.array([[549,235],[549,245],[420,240]], np.int32)
    cv2.fillConvexPoly(frame0,pts2,(0,0,0),cv2.LINE_AA)   
    cv2.fillConvexPoly(frame1,pts2,(0,0,0),cv2.LINE_AA)   
    pts3 = np.array([[315,11],[325,11],[320,140]], np.int32)
    cv2.fillConvexPoly(frame0,pts3,(0,0,0),cv2.LINE_AA)   
    cv2.fillConvexPoly(frame1,pts3,(0,0,0),cv2.LINE_AA)   
    pts4 = np.array([[315,469],[325,469],[320,340]], np.int32)
    cv2.fillConvexPoly(frame0,pts4,(0,0,0),cv2.LINE_AA)
    cv2.fillConvexPoly(frame1,pts4,(0,0,0),cv2.LINE_AA)

    # 上下左右反転
    FlipXY0 = cv2.flip(frame0, -1)
    FlipXY1 = cv2.flip(frame1, -1)
   
    # 名前を入れてみた。
    font = cv2.FONT_HERSHEY_COMPLEX_SMALL
    Text = 'Target Scope & Electronic Projection Cross Gauge'
    cv2.putText(FlipXY0,Text,(10,472), font, 1,(255,255,255),1,cv2.LINE_AA)
    Text = 'Target Scope & Electronic Projection Cross Gauge'
    cv2.putText(FlipXY1,Text,(10,472), font, 1,(255,255,255),1,cv2.LINE_AA)

    # Display the resulting frame
    cv2.imshow('Internal Camera #0    "q":END',FlipXY0)
    cv2.imshow('External MicroScope #1    "q":END',FlipXY1)

    # キー入力"q"で終了
    if cv2.waitKey(1) & 0xFF == ord('q'):
           break


# When everything done, release the capture
cap0.release()
cap1.release()
cv2.destroyAllWindows()
**********

動きました。
左:内蔵カメラの前にパンフレット
右:ユニバーサル基板
遅延はほとんど感じません。
動きも滑らかで15fps以上はあるでしょう。
タスクマネージャーでこのプロセスのCPUパワーみると、7~8%です。
先の映像演算のオーバーレイ照準で2台カメラだと
0.3~0.5秒のディレイがあります。
動きは、ギリギリ滑らかに動いている感じなので
フレームレートは、10~12fpsでしょう。
CPUパワーは40%程になります。
沢山の演算をするには、強力なグラフィックボードが必要ですね。
できたソースファイル名を「TargetScope.py」とすると
メモ帳で
Python TargetScope.py
とだけ書いた ***.batファイルを作成して実行すればいいです。
bCNCの様にPython窓が出た後、カメラのウインドウが出てきます。

ここまで来てふと気づきました。
スコープは円筒で上下の区別はないので
本体を回せば正立像になりますねー。
何と頭の硬いこと!
ということで
上下左右反転の操作は不要です。更に軽くなるはず。
スクリプトはこれに落ち着きました。
外部USBマイクロスコープのみ。
**********
# -*- coding: utf-8 -*-
import numpy as np
import cv2

# カメラからキャプチャする。
# (0):内蔵カメラ   (1):USBカメラ:
cap1 = cv2.VideoCapture(1)

while(True):
    # Capture frame-by-frame
    ret, frame1 = cap1.read()
    # USBカメラが接続されていないとエラーにする
    if not ret:
        print 'error?'
        break

    # LINE描画、線幅:1px
    cv2.line(frame1,(320,0),(320,480),(0,0,0),1)
    cv2.line(frame1,(0,240),(640,240),(0,0,0),1)
    # 最後 -1:塗りつぶし、1:輪郭のみ、cv2.LINE_AA:アンチエイリアス
    cv2.circle(frame1,(320,240), 230, (0,0,0), 1,cv2.LINE_AA)
    cv2.circle(frame1,(320,240), 100, (0,0,0), 1,cv2.LINE_AA)
    cv2.circle(frame1,(320,240),  50, (0,0,255), 1)
    cv2.circle(frame1,(320,240),  25, (255,0,0), 1)
    cv2.circle(frame1,(320,240),  10, (0,255,0), 1)
    # 描画する多角形の頂点座標を配列に格納。
    # 三角形を塗りつぶしている。
    pts1 = np.array([[91,235],[91,245],[220,240]], np.int32)
    cv2.fillConvexPoly(frame1,pts1,(0,0,0),cv2.LINE_AA)
    pts2 = np.array([[549,235],[549,245],[420,240]], np.int32)
    cv2.fillConvexPoly(frame1,pts2,(0,0,0),cv2.LINE_AA)   
    pts3 = np.array([[315,11],[325,11],[320,140]], np.int32)
    cv2.fillConvexPoly(frame1,pts3,(0,0,0),cv2.LINE_AA)   
    pts4 = np.array([[315,469],[325,469],[320,340]], np.int32)
    cv2.fillConvexPoly(frame1,pts4,(0,0,0),cv2.LINE_AA)

    # 名前を入れてみた。
    font = cv2.FONT_HERSHEY_COMPLEX_SMALL
    Text = 'Target Scope & Electronic Projection Cross Gauge'
    cv2.putText(frame1,Text,(10,472), font, 1,(255,255,255),1,cv2.LINE_AA)

    # Display the resulting frame
    cv2.imshow('External-CAM #0    "q":END',frame1)

    # キー入力"q"で終了
    if cv2.waitKey(1) & 0xFF == ord('q'):
          break

# When everything done, release the capture
cap1.release()
cv2.destroyAllWindows()
**********

よっし!
このマイクロスコープ2台でゼロ点調整とX軸の平行調整をできるように
取り付けを考えることにします。
ということでマーティーは、もう1台カメラをポチります。

4 件のコメント:

昔青年 さんのコメント...

こんにちは マーティーさん

 どんどんCNCが進化していきますね。
当方は、Z軸プローブがうまくいったのでXYZ軸にリミットSWを付加してマシーン原点が設定できるようにしたいと思っています。
当方操作中に移動距離の単位設定を確認ミスして、モーターをうならせることが再々ありますので。(笑)
 Fusion360難しいですね。
またぞろ、JW CADに逆戻りし、JW CAD ⇒ NCVC でやってます。
現在は、ケース加工のための、パーツ作りをやりはじめました。
マーティーさん、気が向いたら基板削りのお手本を見せてください。(勝手にリクエストです)

マーティーの工房日誌 さんのコメント...

昔青年さんいつもありがとうございます。
いやー中途半端でなかなか進化という所に至りませんね~(-_-;)
リミットSWいいですね~、私も時々ググググーっとやってしまいます。
部品だけは早くから手に入れているのですが...
基盤のリクエストですかあ!
まだEAGLEを使ったことなくて、それからです。
そういえば、昔青年さんのご助言で、
安かろうと悪いものは悪いときっぱりOpen Disputeするようにしました。あれから2件ほど返金されました。

昔青年 さんのコメント...

返金されたそうでよかったですね。
クレームは結構精神的に疲れます。
心優しい日本人ですからね。
相手は、百戦錬磨の猛者ですから、平気で評価星5個を求めてきますよ(笑)
Eagleは、よくできた参考図書がでていますし、私でもそこそこ使えましたのでマーティーさんなら朝飯前請け合いです。
当方は、オートルーター機能が気に入っています。これ(アートワーク)を考えるのが楽しいと言う先輩方がおられますが。

マーティーの工房日誌 さんのコメント...

辛口の日本人に改心しまーす!
設計屋の時代はパターン手書き、アートワーク手張りの
ALL手作業でした。
表紙の基盤は石油ストーブの上で温めながらエッチングしたものです。
EAGLEのオート機能が楽しみです。
その前に何を作るかですねー