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

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

【GAS】ファイルの最終更新者を取得する方法

 最近ちょくちょくGoogleAppsScriptを触っています。

 今回ちょっと気になってGoogleドライブにあるファイルの最終更新者を取得できないかと調べていたらできたので、その備忘録になります。

 では始めます。


1:Drive APIを有効にする
 Googleドライブにあるファイルの最終更新者を取得するにはDrive APIを有効にする必要があります。

 スクリプトエディタを開いてメニューの「リソース」→「Googleの拡張サービス」を選択します。
f:id:rikoubou:20190426154927p:plain

 Googleの拡張サービス画面が出て来るので、その中にある「Drive API」の右側にある「無効」とあるボタンをクリックします。
f:id:rikoubou:20190426155129p:plain

 すると以下のように「ON」に切り替わるので、その状態で「OK」ボタンをクリックします。
f:id:rikoubou:20190426155237p:plain

 これでDrive APIが有効になりました。


2:最終更新者を取得するソースコード
 参考資料にあるサイト様からのそのままの引用ですが、以下の記述で取得できます。

Logger.log("name:" + Drive.Files.get(file.getId()).lastModifyingUserName); //最終更新者の名前を取得
Logger.log("name:" + Drive.Files.get(file.getId()).lastModifyingUser.displayName); //ユーザーから取得

 その他Drive APIでアクセスできる情報一覧は以下の公式リファレンスにあります。


3:サンプルコード

 以前の記事で作ったものを少し改造して、最終更新者を取得できるようにしてみたサンプルコードが以下になります。

・サンプルコード

// 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: "Get last update user", functionName: "function1"});
  menuEntries.push(null); // line separator
  menuEntries.push({name: "Menu Entry 2", functionName: "function2"});

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

function function1() {
  // 最終更新者取得
  var name = "最終更新者:"
  name = name + Drive.Files.get("[対象ファイルのID]").lastModifyingUserName;
  SpreadsheetApp.getUi().alert(name); // メッセージ通知
}

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

 このfunction1の関数を実行すると以下のようにメッセージ画面に対象ファイルの最終更新者のユーザ名が表示されます。
f:id:rikoubou:20190426161016p:plain

 Drive APIを有効にした初回の実行ではスクリプト実行の許可を設定しないといけない場合があるので、許可の設定をしてください。


おまけ:最終更新者が取得できる場合とできない場合
 最終更新者の取得方法はわかりましたが、実は最終更新者が取得できない場合があります。
 それはファイルの共有設定の違いによってきます。

 この以前の記事でGoogleドライブの共有設定を色々と説明しています。
 その中にある「匿名●●」と表示される共有設定の状態で「匿名●●」のアカウントが対象ファイルを更新した場合、最終更新者を取得すると「undefined」となります。

 つまり『最終更新者のアカウント名をちゃんと取得したい場合は「匿名●●」の共有設定にはしない』ということになります。


 以上がGoogleAppsScriptを使ってファイルの最終更新者を取得する方法です。

 公式リファレンスにもあるように、最終更新者以外の様々な情報も取得できるのでファイル管理を自動化する時には割と役に立ちそうです。


・参考資料

【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】pythonにおける参照渡し/値渡し

 2019/05/20:色々と間違えていたので修正しました。

 今回は割とハマりがちなpythonにおける参照渡しと値渡しについてです。
 関数の引数として渡した場合はどちらなのか? みたいな内容の備忘録です。

 自分も結構はまってしまったので何かの助けになれば幸いです。

 では始めます。


1:pythonはすべて参照渡し
 pythonの処理としては、すべて参照渡しになっています。
 しかし渡された値を変更した際に元の値自体も変更されてしまうかどうかは、以下のようにオブジェクトの型に依存します。

変更不可(Immutable)な型 変更可能(Mutable)な型
int, float, str, tuple, bytes, frozenset 等 list, dict, set, bytearray 等

 例えば引数にintやstrなどを渡してその関数内で書き換えられたとしても、呼び出し元の変数の値には変化はありません。
 しかしlistなどを引数にして関数内で書き換えられた場合は、呼び出し元の変数の値も書き換えられてしまいます。


2:変更可能な型を別オブジェクトとしてコピーしたい場合
 変更可能な型だけど変更されたくない場合は、deepcopy関数を使って別オブジェクトとしてコピーします。

import copy

b = copy.deepcopy(a) # aとbは別オブジェクトになる


3:変更不可な型と変更可能な型のサンプルコード
 pythonでの変更不可な型と変更可能な型がわかったのでそれぞれ軽く検証できるコードを作成しました。

・sampleCode.py

import copy

def main():
    # 変更不可の型の場合
    a = 1
    print("a:", a)
    change_argument_int(a)
    print("a:", a)

    # 変更可能の型の場合
    intList = [1, 2, 3]
    intList2 = intList # 参照渡しなのでintListが変更されるとintList2も変更される
    print("intList:", intList)
    change_argument_list(intList)
    print("intList2:", intList2)

    # 変更可能の型でdeepcopyを使って別オブジェクトにした場合
    intList3 = [1, 2, 3]
    intList4 = copy.deepcopy(intList3) # 別オブジェクトになる
    print("intList3(1st):", intList3)
    change_argument_list(intList3)
    print("intList3(2nd):", intList3)
    print("intList4:", intList4)

# 引数の中身を書き換える(int型なので呼び出し元に影響なし)
def change_argument_int(arg):
    arg = 200

# 引数の中身を書き換える(list型なので呼び出し元に影響あり)
def change_argument_list(intList):
    intList.append(10)

if __name__ == '__main__':
    main()

 上記を実行すると以下のように、変更不可な型と変更可能な型のそれぞれの場合で動いていることがわかります。

a: 1
a: 1
intList: [1, 2, 3]
intList2: [1, 2, 3, 10]
intList3(1st): [1, 2, 3]
intList3(2nd): [1, 2, 3, 10]
intList4: [1, 2, 3]


 以上がpythonにおける参照渡し/値渡しについてです。

 初期化の時の型だったり、引数の型だったりで意図しない動きになる場合があるので注意が必要です。(自戒も含めて…)


・参考資料

【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を使って画像から直線を抽出する方法です。

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

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


・参考資料