ソースに絡まるエスカルゴ

貧弱プログラマの外部記憶装置です。

【Googleドライブ】Googleドライブでの色々なファイル/フォルダ共有方法

 今回はタイトルにある通りGoogleドライブでのファイルやフォルダの共有方法です。
 基本的にはファイルもフォルダも同じような方法で共有できます。

 共有と一言に言っても「閲覧のみ」や「編集可」など色々設定することができるのでそれらの情報をまとめたものになります。

 では始めます。


1:共有方法
 単に手っ取り早く対象のファイルを共有したい場合は、Googleドライブ上で対象ファイルやフォルダを右クリックして「共有可能なリンクを取得」を選択します。
f:id:rikoubou:20190423152438p:plain

 すると以下のような表示になります。クリップボードに対象のリンクかコピーされてます。
f:id:rikoubou:20190423152647p:plain

 この状態で「Ctrl+V」で共有リンクを貼り付けることができます。

 共有リンクを別のGoogleアカウントで開いた場合、基本的には以下のように「閲覧のみ」になり編集はできません。またアドオンやGASの実行もできません。
f:id:rikoubou:20190423153117p:plain

 ちなみに許可されたユーザ以外がこのファイルを開いていた場合、「匿名●●」のように右上に表示されます。
f:id:rikoubou:20190423153526p:plain

 この「匿名●●」には「匿名イフリート」などレアだったり変な名前だったりのものもあるので、マニアな人は調べてみるといいかもしれません。


2:共有設定の変更
 共有設定には「閲覧可」、「コメント可」、「編集可」の3種類あります。1での設定では「閲覧可」になっています。

 共有設定を変更する場合は共有しているファイルを右クリックして「共有」を選択します。
f:id:rikoubou:20190423154659p:plain

 次に「リンクを知っている全員が閲覧可」をクリックします。
f:id:rikoubou:20190423155011p:plain

 プルダウンが出てくるので設定したい項目にチェックを入れて「完了」ボタンを押すと変更できます。
f:id:rikoubou:20190423155151p:plain

 ちなみに「コメント可」の場合に別アカウントで開くと以下のようになります。
f:id:rikoubou:20190423155514p:plain

 次に「編集可」の場合に別アカウントで開くと以下のようになります。
f:id:rikoubou:20190423155549p:plain

 ファイルのアドオンやGASを誰でも使えるようにするには「編集可」の権限を与えておく必要があります。

 また、この状態でもアクセスしているのは不特定のアカウントになるので右上にはアクセスしている「匿名●●」と表示されます。

 ちなみに共有をやめたい場合は、プルダウン時に出てくる「オフ」を選択すると共有をやめることができます。
f:id:rikoubou:20190423160126p:plain


3:特定アカウントの共有設定
 不特定多数ではなく、個別のアカウントにだけ共有したい場合もあります。

 その場合はまず対象を右クリックして「共有」を選択します。
f:id:rikoubou:20190423154659p:plain

 3の最後に書いたやり方で共有を「オフ」にし、その下にある「ユーザー」のところに許可したい名前かメールアドレスを入力します。
f:id:rikoubou:20190423161010p:plain

 許可するユーザーを入力すると以下のようになるので「送信」ボタンをクリックします。
f:id:rikoubou:20190423161102p:plain

 これで対象のアカウントに以下のようなメールが届くので、アクセスできるようになります。
f:id:rikoubou:20190423161244p:plain

 この方法で共有するとデフォルトで「編集可」の権限となるので注意してください。

 ちなみにこの方法で共有すると、右上に表示されるものが「匿名●●」ではなく許可したアカウントがそのまま表示されます。


4:アカウント別の共有設定
 3の方法でアカウント個別に共有設定を変更することも可能です。

 対象を右クリックして「共有」を選択しましす。
f:id:rikoubou:20190423154659p:plain

 右下にある「詳細設定」をクリックします。
f:id:rikoubou:20190423161847p:plain

 すると許可されたアカウント一覧が表示されるので、プルダウンから個別に設定することができます。
f:id:rikoubou:20190423161926p:plain


5:フォルダの共有設定
 1〜4まで紹介した共有設定はフォルダ自体に設定することもできます。

 ただしフォルダ自体に設定した場合、そのフォルダ配下にあるファイルやフォルダも同様の共有設定になるため注意が必要です。


 以上がGoogleドライブにおけるファイルやフォルダの共有方法です。

 どういう場合に匿名●●と表示されるのか、GASが有効になるのか、など自分の中でもよくわからなかったので今回である程度整理できたかと思います。

 不特定多数に共有したいのか、特定のアカウントのみに共有したいのか、また権限はどれぐらい与えるのか、匿名●●を表示させたいのか、などをしっかり考えた上でGoogleドライブを使いこなしたいです。


・参考資料

【GAS/Googleスプレッドシート】Googleスプレッドシートを使ってGoogle Apps Scriptを試してみる

 Googleアカウントを持っている人はGoogleスプレッドシートなどを使っている方も多いかと思います。

 ただ単にWordやExcelの代用として使うだけでなく「Google Apps Script(GAS)」を使ってプログラムを記述することで機能を拡張することができます。
 Excelにおけるマクロみたいなものですが、GoogleだけあってGmailと連携させたりなど様々な機能が充実しています。
 参考資料のところにある公式リファレンスのリンクを参照すると、様々な機能が用意されていることがわかると思います。

 今回はその一歩目としてGoogleスプレッドシートを使ってひとまずGASを試してみた備忘録になります。

 では始めます。


1:スプレッドシートを作成してスクリプトエディタを開く
 まず最初にGoogleドライブ上で右クリックからスプレッドシートを新規作成します。
f:id:rikoubou:20190422154916p:plain

 スプレッドシートが作成されるので、メニューにある「ツール」→「スクリプトエディタ」を選択します。
f:id:rikoubou:20190422154620p:plain

 以下のようなスクリプトを記述できる画面になります。ここに色々と記述していけば様々な機能を実現できます。
f:id:rikoubou:20190422155856p:plain


2:メニューを追加する
 手始めとしてGoogleスプレッドシートにオリジナルのメニューを追加してみます。

 リファレンスを見るとまさにその関数のサンプルが見つかります。
f:id:rikoubou:20190422160648p:plain

 なのでそのままコピペします。

// The onOpen function is executed automatically every time a Spreadsheet is loaded
function onOpen() {
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var menuEntries = [];
  // When the user clicks on "addMenuExample" then "Menu Entry 1", the function function1 is
  // executed.
  menuEntries.push({name: "Menu Entry 1", functionName: "function1"});
  menuEntries.push(null); // line separator
  menuEntries.push({name: "Menu Entry 2", functionName: "function2"});

  ss.addMenu("addMenuExample", menuEntries);
}

function myFunction() {
  
}

 onOpen()はファイルが開かれた時に自動的に実行される関数で、その関数内でメニューを追加する処理が記述されています。
 またmenuEntries.pushのところの「functionName」が呼ばれている関数なので、それぞれ呼び出し先のfunction1とfunction2を別途実装する必要があります。

 メニューが追加されているかの確認はスクリプトエディタの記述を保存し、Googleスプレッドシートを終了させてからもう一度開きます。

 以下のようにメニューが追加されていることが確認できます。
f:id:rikoubou:20190422161002p:plain


3:メッセージ通知を表示させる
 メニューを追加しましたが、メニューが選択された時に動く呼び出し先の関数はまだ実装できていません。
 なので今回はメッセージ通知を表示させたいと思います。

 メッセージ通知は以下の記述でできます。

SpreadsheetApp.getUi().alert("メッセージ");

 メッセージ通知のやり方がわかったので、2のスクリプトを以下のように修正します。

// The onOpen function is executed automatically every time a Spreadsheet is loaded
function onOpen() {
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var menuEntries = [];
  // When the user clicks on "addMenuExample" then "Menu Entry 1", the function function1 is
  // executed.
  menuEntries.push({name: "Menu Entry 1", functionName: "function1"});
  menuEntries.push(null); // line separator
  menuEntries.push({name: "Menu Entry 2", functionName: "function2"});

  ss.addMenu("addMenuExample", menuEntries);
}

function function1() {
  SpreadsheetApp.getUi().alert("function1が呼ばれました"); // メッセージ通知
}

function function2() {
  SpreadsheetApp.getUi().alert("function2が呼ばれました"); // メッセージ通知
}

 function1やfunction2など個別の関数の動作確認をしたい場合は、以下のようにプルダウンから実行したい関数を選択して再生ボタンをクリックすると実行できます。
f:id:rikoubou:20190422161830p:plain

 ただし初めて実行する場合は以下のような警告が出ます(Chromeの場合)。その場合は「許可を確認」ボタンをクリックします。
f:id:rikoubou:20190422162001p:plain

 するとGoogleアカウントの選択画面になるので使用するアカウントを選択します。
f:id:rikoubou:20190422162122p:plain

「このアプリは確認されていません」と出るので詳細をクリックして一番下にある「安全ではないページに移動」をクリックします。
f:id:rikoubou:20190422162159p:plain
f:id:rikoubou:20190422162209p:plain

 自分のスクリプトの内容であることを確認し「許可」ボタンをクリックして許可を付与します。
f:id:rikoubou:20190422162413p:plain

 一度許可しておけばもうこの設定をする必要はなくなります。

 この状態でfunction1を実行すると以下のように「関数function1を実行中」と表示されます。
f:id:rikoubou:20190422162710p:plain

 スプレッドシートへ切り替えるとちゃんとメッセージ通知が表示されており「OK」ボタンをクリックするとfunction1の処理が終了します。
f:id:rikoubou:20190422162817p:plain

 もちろんスプレッドシートの追加したメニューのところから実行しても同じ結果になります。


 以上がスプレッドシートを使ってGASを試してみた内容です。

 今回は初歩的な内容でしたが、CSVなどでGoogleドライブ上にデータを上げることができればスプレッドシートとGASを組み合わせてIoT的なことも割と簡単にできそうです。

 GASを調べていけば様々な場面に使えそうなので、ちょっとずつでも勉強していきたいです。


・参考資料

【TinkerBoard/OpenCV】TinkerBoardにOpenCVの環境を構築する

www.asus.com

 最近TinkerBoardと呼ばれるRaspberry Piのより強い版みたいなボードPCを触る機会がありました。

 環境構築は毎回つまづくことが多いので、まっさらな状態からOpenCVをインストールするまでにやったことの備忘録です。

 では始めます。


1:TinkerBoardのOSをダウンロードしてSDカードに書き込む
 Raspberry Piと同じようにSDカードにOSを書き込みます。
 以下のページから対象のOSを選択してダウンロードし、SDカードに書き込みます。

 ※以降OSのバージョンは「v2.0.4」という前提で進めます。


2:Wifiへの接続
 OSの書き込みが終了したら本体にSDカードをセットし、TinkerBoardを起動します。起動したら以下のページ様にあるように、右下のアイコンからWifiに接続します。

 ブラウザ等を起動させてインターネットに接続していることを確認します。


3:各種パッケージの最新化
 インターネットに接続したことを確認したら、以下のコマンドを実行して各種パッケージを最新化します。

$ sudo apt-get update && sudo apt-get -y upgrade


4:python3のpipをインストール
 パッケージの最新化ができたら、以下のコマンドを実行してpython3のpipをインストールします。

$ sudo apt-get install python3-pip


5:pipでOpenCVをインストールできるようpip.confファイルを作成する
 そのままだとpipでOpenCVをインストールできないので、pip.confを作成します。

 以下の2つのコマンドを実行してpip.confファイルを新規作成します。

$ cd /etc
$ sudo leafpad pip.conf

 実行するとテキストエディタが開くので、以下の内容を記述して保存します。

・pip.conf

[global]
extra-index-url=https://www.piwheels.hostedpi.com/simple


6:OpenCVをインストールする
 pip.confファイルを保存できたら、以下のコマンドを実行してOpenCVをインストールします。

$ pip3 install opencv-python

 このコマンドを実行すると何度かエラーが出る場合がありますが、何度かリトライするとエラーなくインストールが完了します。


7:python3でimport cv2を実行してOpenCVが使えるかを確認する
 以下のコマンドでpython3を起動させ、import cv2を実行してエラーがでなければOpenCVが使えるようになっています。

$ python3
$ >>import cv2


8:import cv2でエラーが出た場合の対処方法
 import cv2を実行した時に色々とエラーが出る場合があります。基本的にはライブラリが不足していたりなどが原因なので、エラーから必要なライブラリを読み取ってインストールしていけばよいです。

 以下、自分の場合に出たエラーとインストールコマンドを列挙しておきます。

・numpy

$ pip3 install numpy

・libatlas

$ sudo apt-get install libatlas-base-dev

・libjasper

$ wget http://ftp.debian.org/debian/pool/main/j/jasper/libjasper-dev_1.900.1-debian1-2.4+deb8u3_armhf.deb
$ wget http://ftp.debian.org/debian/pool/main/j/jasper/libjasper1_1.900.1-debian1-2.4+deb8u3_armhf.deb
$ sudo apt-get install ./libjasper*.deb

・QT関連

$ sudo apt-get install qt4-dev-tools qt4-doc qt4-qtconfig libqt4-test


 以上がTinkerBoardでOpenCVの環境を構築するまでの手順です。

 OSのバージョンの違いだったり、ちょっとした環境の違いだったりでこの手順通りには行かない場合もあるので、参考程度にしていただければと思います。
 またTinkerOSはDebian系なのでRaspberry Piでも同様の方法でOpenCVの環境が構築できるかとは思います(未検証)。

 環境構築は一度やれば終わりで結構忘れてしまうので、できる限りメモしていきたいです…。


・参考資料

【python】線分ABと線分CDの交点(2つの直線の交点)を計算する

 以前の記事でハフ変換を使って画像から直線を求めました。

 
 直線を出したら交点も求めたくなるのではないか、という気がするので今回はタイトル通りに2つの直線の交点を計算する方法についての備忘録です。

 では始めます。


1:交点の計算方法
 詳しい考え方は参考資料の以下のページ様に解説があるのでそちらを確認してください。

 この方法をpythonで書くと以下のようになります。

# 線分ABと線分CDの交点を求める関数
def _calc_cross_point(pointA, pointB, pointC, pointD):
    cross_point = (0,0)
    bunbo = (pointB[0] - pointA[0]) * (pointD[1] - pointC[1]) - (pointB[1] - pointA[1]) * (pointD[0] - pointC[0])

    # 直線が平行な場合
    if (bunbo == 0):
        return False, cross_point

    vectorAC = ((pointC[0] - pointA[0]), (pointC[1] - pointA[1]))
    r = ((pointD[1] - pointC[1]) * vectorAC[0] - (pointD[0] - pointC[0]) * vectorAC[1]) / bunbo
    s = ((pointB[1] - pointA[1]) * vectorAC[0] - (pointB[0] - pointA[0]) * vectorAC[1]) / bunbo

    # rを使った計算の場合
    distance = ((pointB[0] - pointA[0]) * r, (pointB[1] - pointA[1]) * r)
    cross_point = (int(pointA[0] + distance[0]), int(pointA[1] + distance[1]))

    # sを使った計算の場合
    # distance = ((pointD[0] - pointC[0]) * s, (pointD[1] - pointC[1]) * s)
    # cross_point = (int(pointC[0] + distance[0]), int(pointC[1] + distance[1]))

    return True, cross_point


2:実際に交点を計算してみる
 計算方法がわかったので前回作成したハフ変換と組み合わせて交点を計算してみます。

・houghLinesCross.py

import numpy as np
import cv2

IMAGE_PATH = "./test.jpg" # 読み込む画像

def main():
    image  = cv2.imread(IMAGE_PATH) # 画像読み込み
    image2 = cv2.imread(IMAGE_PATH) # 画像読み込み

    gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY) # グレースケール化
    outLineImage = cv2.Canny(gray, 220, 250, apertureSize = 3)   # 輪郭線抽出
    cv2.imwrite("./outLine.png", outLineImage)    # ファイル保存

    houghList  = hough_lines(image, outLineImage)  # ハフ変換による直線抽出
    cv2.imwrite("./result_hough.png", image)       # ファイル保存
    draw_cross_points(image, houghList)            # 直線リストから交点を描画
    cv2.imwrite("./result_hough_cross.png", image) # ファイル保存

    houghPList = hough_lines_p(image2, outLineImage)  # 確率的ハフ変換による直線抽出
    cv2.imwrite("./result_houghP.png", image2)        # ファイル保存
    draw_cross_points(image2, houghPList)             # 直線リストから交点を描画
    cv2.imwrite("./result_houghP_cross.png", image2)  # ファイル保存


# ハフ変換で直線を抽出する関数
def hough_lines(image, outLineImage):
    lineList = []
    lines = cv2.HoughLines(outLineImage, rho=1, theta=np.pi/180, threshold=200) # ハフ変換で直線抽出
    print("hough_lines: ", len(lines))

    for line in lines:
        rho, theta = line[0]
        a = np.cos(theta)
        b = np.sin(theta)
        x0 = a*rho
        y0 = b*rho
        x1 = int(x0 + 1000*(-b))
        y1 = int(y0 + 1000*(a))
        x2 = int(x0 - 1000*(-b))
        y2 = int(y0 - 1000*(a))
        lineList.append((x1, y1, x2, y2))

        cv2.line(image,(x1,y1),(x2,y2),(0,0,255),2) # 赤色で直線を引く

    return lineList


# 確率的ハフ変換で直線を抽出する関数
def hough_lines_p(image, outLineImage):
    lineList = []
    # 確率的ハフ変換で直線を抽出
    lines = cv2.HoughLinesP(outLineImage, rho=1, theta=np.pi/180, threshold=200, minLineLength=100, maxLineGap=100)
    print("hough_lines_p: ", len(lines))

    for line in lines:
        x1, y1, x2, y2 = line[0]
        lineList.append((x1, y1, x2, y2))
        cv2.line(image,(x1,y1),(x2,y2),(0,255,0),2) # 緑色で直線を引く

    return lineList


# 交点を描画する関数
def draw_cross_points(image, lineList):
    size = len(lineList)

    cnt = 0
    for i in range(size-1):
        for j in range(i+1, size):
            pointA = (lineList[i][0], lineList[i][1])
            pointB = (lineList[i][2], lineList[i][3])
            pointC = (lineList[j][0], lineList[j][1])
            pointD = (lineList[j][2], lineList[j][3])
            ret, cross_point = calc_cross_point(pointA, pointB, pointC, pointD) # 交点を計算
            if ret:
                # 交点が取得できた場合でも画像の範囲外のものは除外
                if (cross_point[0] >= 0) and (cross_point[0] <= image.shape[1]) and (cross_point[1] >= 0) and (cross_point[1] <= image.shape[0]) :
                    cv2.circle(image, (cross_point[0],cross_point[1]), 2, (255,0,0), 3) # 交点を青色で描画
                    cnt = cnt + 1
    print("draw_cross_points:", cnt)


# 線分ABと線分CDの交点を求める関数
def calc_cross_point(pointA, pointB, pointC, pointD):
    cross_points = (0,0)
    bunbo = (pointB[0] - pointA[0]) * (pointD[1] - pointC[1]) - (pointB[1] - pointA[1]) * (pointD[0] - pointC[0])

    # 直線が平行な場合
    if (bunbo == 0):
        return False, cross_points

    vectorAC = ((pointC[0] - pointA[0]), (pointC[1] - pointA[1]))
    r = ((pointD[1] - pointC[1]) * vectorAC[0] - (pointD[0] - pointC[0]) * vectorAC[1]) / bunbo
    s = ((pointB[1] - pointA[1]) * vectorAC[0] - (pointB[0] - pointA[0]) * vectorAC[1]) / bunbo

    # 線分AB、線分AC上に存在しない場合
    if (r <= 0) or (1 <= r) or (s <= 0) or (1 <= s):
        return False, cross_points

    # rを使った計算の場合
    distance = ((pointB[0] - pointA[0]) * r, (pointB[1] - pointA[1]) * r)
    cross_points = (int(pointA[0] + distance[0]), int(pointA[1] + distance[1]))

    # sを使った計算の場合
    # distance = ((pointD[0] - pointC[0]) * s, (pointD[1] - pointC[1]) * s)
    # cross_points = (int(pointC[0] + distance[0]), int(pointC[1] + distance[1]))

    return True, cross_points

if __name__ == '__main__':
    main()

 上記を実行した結果は以下のようになります。

・元画像
f:id:rikoubou:20190403162904j:plain

・結果(左:ハフ変換 右:確率的ハフ変換)

f:id:rikoubou:20190403162953p:plainf:id:rikoubou:20190403163001p:plain

 交点に青色の点がプロットされているのがわかるかと思います。


 以上が2つの直線の交点を計算する方法です。

 カードの直線を検出してその角の座標を取得したい場合に使うといいかもしれません。

 また今回作成したソースコードは以下に公開しておきます。


・参考資料

【python/OpenCV】読み込んだ画像から直線を抽出する方法

 今回はタイトルにある通りpythonOpenCVを使って読み込んだ画像から直線を抽出する方法の備忘録です。
 基本的には参考資料に挙げたページ様からの引用ですので、詳しく知りたい方はそちらを参照してください。

 直線を抽出する大まかな流れとしては以下の通りです。

  1. 画像読み込み
  2. グレースケール化
  3. 輪郭抽出
  4. ハフ変換による直線抽出
  5. 直線描画

 では説明していきます。


1:画像読み込み〜輪郭抽出
 画像の読み込みから輪郭抽出までは以下の方法でできます。

import cv2

img = cv2.imread("./test.jpg") # 画像読み込み
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) # グレースケール化
outLineImage = cv2.Canny(gray, 220, 250, apertureSize = 3)   # 輪郭線抽出

 Canny関数で線の部分を白色、他の部分を黒色として抽出した画像を取得しています。各引数の値は読み込む画像によってうまく調節する必要があります。


2:ハフ変換による直線抽出
 直線を抽出するには「ハフ変換」を使います。OpenCVではハフ変換を使って直線を抽出する関数が2種類あるので、両方説明していきます。

・ハフ変換(HoughLines関数)の場合
 HoughLines関数を使って直線を得る場合は以下の記述でできます。

import numpy as np
import cv2

# ハフ変換で直線抽出
lines = cv2.HoughLines(outLineImage, rho=1, theta=np.pi/180, threshold=200)

for line in lines:
    rho, theta = line[0]
    a = np.cos(theta)
    b = np.sin(theta)
    x0 = a*rho
    y0 = b*rho
    x1 = int(x0 + 1000*(-b))
    y1 = int(y0 + 1000*(a))
    x2 = int(x0 - 1000*(-b))
    y2 = int(y0 - 1000*(a))

    cv2.line(result,(x1,y1),(x2,y2),(0,0,255),2) # 赤色で直線を引く

 第一引数は輪郭線の画像、第二引数と第三引数は参考資料を見ていただくとして、とりあえず定数として上記の値を設定しています。
 第四引数は直線と認定する最低限の値を設定します。つまり値を小さくすると直線として判定されやすくなり、値を大きくすると直線として判定されにくくなります。


・確率的ハフ変換(HoughLinesP関数)の場合
 HoughLinesP関数を使って直線を得る場合は以下の記述でできます。ちなみに確率的ハフ変換の方が計算量が少なくて済みます。

import numpy as np
import cv2

# 確率的ハフ変換で直線を抽出
lines = cv2.HoughLinesP(outLineImage, rho=1, theta=np.pi/180, threshold=200, minLineLength=100, maxLineGap=100)

for line in lines:
    x1, y1, x2, y2 = line[0]
    cv2.line(resultP,(x1,y1),(x2,y2),(0,255,0),2) # 緑色で直線を引く

 第四引数までは先ほどと同じですが、第五引数と第六引数が追加されています。
 第五引数は線とみなす最低の長さを設定します。つまり値が大きいほど長い直線でないと認識されなくなります。
 第六引数は同一線上にある点の間隔の最大値を設定します。つまり値が大きいほど離れた点の直線を認識できるようになります。


3:サンプルコード
 画像から直線を抽出する方法がわかったので、2のやり方の両方を実装してみたサンプルコードが以下になります。

・houghLines.py

import numpy as np
import cv2

IMAGE_PATH = "./test.jpg" # 読み込む画像

def main():
    image  = cv2.imread(IMAGE_PATH) # 画像読み込み
    image2 = cv2.imread(IMAGE_PATH) # 画像読み込み
    gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY) # グレースケール化
    outLineImage = cv2.Canny(gray, 220, 250, apertureSize = 3)   # 輪郭線抽出

    cv2.imwrite("./outLine.png", outLineImage)    # ファイル保存

    hough  = hough_lines(image, outLineImage)     # ハフ変換による直線抽出
    cv2.imwrite("./result_hough.png", image)    # ファイル保存

    houghP = hough_lines_p(image2, outLineImage)   # 確率的ハフ変換による直線抽出
    cv2.imwrite("./result_houghP.png", image2)  # ファイル保存


# ハフ変換で直線を抽出する関数
def hough_lines(image, outLineImage):
    result = image
    lines = cv2.HoughLines(outLineImage, rho=1, theta=np.pi/180, threshold=200) # ハフ変換で直線抽出
    print("hough_lines: ", len(lines))

    for line in lines:
        rho, theta = line[0]
        a = np.cos(theta)
        b = np.sin(theta)
        x0 = a*rho
        y0 = b*rho
        x1 = int(x0 + 1000*(-b))
        y1 = int(y0 + 1000*(a))
        x2 = int(x0 - 1000*(-b))
        y2 = int(y0 - 1000*(a))

        cv2.line(result,(x1,y1),(x2,y2),(0,0,255),2) # 赤色で直線を引く

    return result


# 確率的ハフ変換で直線を抽出する関数
def hough_lines_p(image, outLineImage):
    resultP = image
    # 確率的ハフ変換で直線を抽出
    lines = cv2.HoughLinesP(outLineImage, rho=1, theta=np.pi/180, threshold=200, minLineLength=100, maxLineGap=100)
    print("hough_lines_p: ", len(lines))

    for line in lines:
        x1, y1, x2, y2 = line[0]
        cv2.line(resultP,(x1,y1),(x2,y2),(0,255,0),2) # 緑色で直線を引く

    return resultP


if __name__ == '__main__':
    main()

 上記のサンプルコードを実行すると、以下のようになります。

・元画像
f:id:rikoubou:20190327201608j:plain

・左:ハフ変換 右:確率的ハフ変換

f:id:rikoubou:20190327201637p:plainf:id:rikoubou:20190327201647p:plain


 以上がpythonOpenCVを使って画像から直線を抽出する方法です。

 カードなどを識別する時にはこの方法を応用することになるのかなと思います。
 ただ写真でやってみると余計なところに線が引かれてしまうので、線が引かれたくないところは黒塗りにしておく必要がありそうです。

 今回作成したサンプルコードも一応公開しておきます。


・参考資料

【python/OpenCV】カメラの歪み補正を行う方法

 カメラのレンズの種類によっては画像自体に歪みが発生する場合があります。(広角レンズなど)

 そのカメラの歪みをpythonOpenCVを使って補正する方法を説明していきます。

 基本的には参考資料にあるページ様のソースコードそのままなので、詳しい内容はそちらを参照してください。

 では始めます。


1:チェスボードの準備
 カメラの歪み補正にはチェスボードを用います。ネットで画像検索すれば色々見つかりますが、自分が適当に作成したものがあるので面倒な方は以下のpdfファイルをダウンロードして印刷して使ってください。

 また、あとで使用するのでマス目の1辺の長さも定規で計測しておくとよいです。


2:補正したいカメラを使ってチェスボードを撮影する
 チェスボードの準備ができたら、そのチェスボードを机なりに置いた状態で補正したいカメラを使って様々角度からチェスボードを撮影します。

 撮影する時は以前記事で紹介した画像キャプチャ用のソースコードを使うと楽かと思います。

 以下が実際に撮影した写真の例です。このように様々な角度からチェスボードを撮影します。
f:id:rikoubou:20190326133857p:plain
f:id:rikoubou:20190326140115p:plain
f:id:rikoubou:20190326135818p:plain

 歪みを補正する精度を高めるためにはチェスボードが写っている画像が20枚ほど必要になるので、頑張って撮影してください。


3:補正用内部パラメータの計算
 チェスボードの画像を20枚ほど撮影できたら、それらの画像を使って補正用のパラメータを計算していきます。
 ソースコードの中身はほとんど以下の参考ページ様からのコピペです。

・calcCamera.py

#
# カメラの歪みを戻すための値を計算する
#
import numpy as np
import cv2
import glob
from time import sleep
from datetime import datetime

TMP_FOLDER_PATH = "./tmp/"
MTX_PATH = TMP_FOLDER_PATH + "mtx.csv"
DIST_PATH = TMP_FOLDER_PATH + "dist.csv"

# メイン関数
def main():
    calcCamera() # カメラの歪みを計算

# カメラの歪みを計算する関数
def calcCamera():
    square_size = 20.0      # 正方形のサイズ(mm)
    pattern_size = (9, 6)  # 模様のサイズ
    pattern_points = np.zeros( (np.prod(pattern_size), 3), np.float32 ) #チェスボード(X,Y,Z)座標の指定 (Z=0)
    pattern_points[:,:2] = np.indices(pattern_size).T.reshape(-1, 2)
    pattern_points *= square_size
    obj_points = []
    img_points = []
 
    for fn in glob.glob("./img/*"):
        # 画像の取得
        im = cv2.imread(fn,0)
        print ("loading..." + fn)
        # チェスボードのコーナーを検出
        found, corner = cv2.findChessboardCorners(im, pattern_size)
        # コーナーがあれば
        if found:
            term = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_COUNT, 30, 0.1)
            corners2 = cv2.cornerSubPix(im, corner, (5,5), (-1,-1), term)
            # マークをつけて画像保存
            img = cv2.drawChessboardCorners(im, pattern_size, corners2, found)
            saveImgByTime(TMP_FOLDER_PATH, img)
            sleep(1)
        # コーナーがない場合のエラー処理
        if not found:
            print ('chessboard not found')
            continue
        img_points.append(corner.reshape(-1, 2))   #appendメソッド:リストの最後に因数のオブジェクトを追加
        obj_points.append(pattern_points)
 
    # 内部パラメータを計算
    rms, K, d, r, t = cv2.calibrateCamera(obj_points, img_points, (im.shape[1],im.shape[0]), None, None)

    # 計算結果を表示
    print ("RMS = ", rms)
    print ("K = \n", K)
    print ("d = ", d.ravel())

    # ファイル保存
    saveCalibrationFile(K, d, MTX_PATH, DIST_PATH)

# キャリブレーションCSVファイルを上書き保存する関数
def saveCalibrationFile(mtx, dist, mtx_path, dist_path):
    np.savetxt(mtx_path, mtx, delimiter =',',fmt="%0.14f")   #カメラ行列の保存
    np.savetxt(dist_path, dist, delimiter =',',fmt="%0.14f") #歪み係数の保存

# 画像を時刻で保存する関数
def saveImgByTime(dirPath, img):
    # 時刻を取得
    date = datetime.now().strftime("%Y%m%d_%H%M%S")
    path = dirPath + date + ".png"
    cv2.imwrite(path, img) # ファイル保存
    print("saved: ", path)

if __name__ == '__main__':
    main()

 calcCamera関数の「square_size = 20.0 # 正方形のサイズ(mm)」のところは1で使用したチェスボードのマス1辺の長さの値に書き換えてください。

 プログラムを少し説明すると、チェスボードの角を検出し、その角からカメラの内部パラメータ(レンズの歪みなど)を計算しています。

 使い方としては上記のプログラムを「calcCamera.py」として保存し、同じ階層に「img」フォルダと「tmp」フォルダを作成します。そしてimgフォルダ内に2で撮影した画像を全て入れ、calcCamera.pyを実行します。
 「tmp」フォルダに角を検出した結果の各白黒画像とカメラの内部パラメータを保存した「mtx.csv」と「dist.csv」が保存されます。


4:内部パラメータを使って画像を補正
 内部パラメータを保存した「mtx.csv」と「dist.csv」を使って画像を補正していきます。

・calibrate.py

#
# カメラの歪みを補正する
#
import numpy as np
import cv2
import glob
from time import sleep
from datetime import datetime

TMP_FOLDER_PATH = "./tmp/"
MTX_PATH = TMP_FOLDER_PATH + "mtx.csv"
DIST_PATH = TMP_FOLDER_PATH + "dist.csv"
SAVE_FOLDER_PATH = "./result/"

# メイン関数
def main():
    calibrateImage() # 画像の歪みを補正

# カメラの歪みをCSVファイルを元に補正する関数
def calibrateImage():
    mtx, dist = loadCalibrationFile(MTX_PATH, DIST_PATH)

    for fn in glob.glob("./img/*"):
        img = cv2.imread(fn)
        resultImg = cv2.undistort(img, mtx, dist, None) # 内部パラメータを元に画像補正
        saveImgByTime(SAVE_FOLDER_PATH, resultImg)
        sleep(1)

# キャリブレーションCSVファイルを読み込む関数
def loadCalibrationFile(mtx_path, dist_path):
    try:
        mtx = np.loadtxt(mtx_path, delimiter=',')
        dist = np.loadtxt(dist_path, delimiter=',')
    except Exception as e:
        raise e
    return mtx, dist

# 画像を時刻で保存する関数
def saveImgByTime(dirPath, img):
    # 時刻を取得
    date = datetime.now().strftime("%Y%m%d_%H%M%S")
    path = dirPath + date + ".png"
    cv2.imwrite(path, img) # ファイル保存
    print("saved: ", path)

if __name__ == '__main__':
    main()

 上記をコピペして「calibrate.py」という名前でcalcCamera.pyと同じ階層に保存します。また同じ階層に「result」フォルダも作成します。
 その状態で実行すると、tmpフォルダの中にあるmtx.csvとdist.csvを読み込み、その値を使ってimgフォルダの中にある画像を補正したものがresultフォルダの中に保存されます。

 ちなみに自分がやった感じだと以下のようになりました。

  • 元画像

f:id:rikoubou:20190326142524p:plain

  • 補正後

f:id:rikoubou:20190326142549p:plain


 以上がpythonOpenCVを使ってカメラの歪み補正を行う方法です。

 あくまでOpenCVがカメラの歪みを推定しているだけなので完璧な補正というわけにはいきませんが、ぱっと見で違和感ない程度には補正されているのがわかるかと思います。それと補正時に元画像の一部がカットされてしまうので注意が必要です。

 画像解析などをする際には歪みがあると何かと面倒だったりする場合もあるので、この方法を使うと何かといいかもしれません。


 またコピペしたりフォルダを作成したりするのが面倒な人もいるかと思うので、3と4のソースコードを1つにまとめた「calibrateCamera.py」やその他諸々をまとめたzipファイルを以下に公開しておきます。必要な方はご自由にどうぞ。


・参考資料

【MacOS】特定のファイルを含まずにzip圧縮する方法

 MacOSユーザーならわかるかとは思いますが、気づいたらディレクトリに「.DS_Store」というファイルが作成されています。これ自体はMac側が管理しやすくするための隠しファイルらしく、ウイルスなどではなく特にあっても問題はありません。

 ですが、Windows側とのファイルの受け渡しなどで「.DS_Store」というファイルまで入っているとなんだか気持ちが悪いです。

 なので今回は上記のようにzip圧縮する際に特定のファイルを含めないコマンドの紹介です。


・特定のファイルを含まずにzip圧縮するコマンド
 以下のコマンドでできます。

$ zip [保存したいzipファイル名].zip -r [圧縮したいディレクトリ名]/ -x "[含めたくないファイル名]"

 具体例として「hogeDirというディレクトリをhogeDir.zipに圧縮する際、.DS_Storeを除外する」という場合は以下のようなコマンドになります。

$ zip hogeDir.zip -r hogeDir/ -x "*.DS_Store"


 以上が特定のファイルを含まずにzip圧縮する方法です。参考資料にあるページ様に、まさに欲しい情報があったので助かりました。


・参考資料