tohokuaikiのチラシの裏

技術的ネタとか。

Deployerを使ってLaravelを本番環境にデプロイする場合のsshの接続設定

開発して、本番環境にDeployのたびにsshログインしてmigrateとかするのが面倒…というか忘れるので、Deployerというのを使った。

インストール

composerからインストール

LaravelアプリケーションDirectoryでComposerでインストール。

$ php -d memory_limit=-1 ~/bin/composer require deployer/deployer --dev

なんか、開発Vagrantがしょぼいのかメモリエラーが出るので -dオプションが必要になってる…

設定ファイル deploy.phpを作る

$ vendor/bin/dep init

すると対話形式の後、deploy.phpができる。既にあるgitリポジトリからremoteを察知してくれるみたい。便利

設定

hostsの部分があるけど、

<?php
// Hosts
inventory('deploy/hosts.yml');
// host('project.com')
//    ->set('deploy_path', '~/{{application}}');    

としてyamlファイルにまとめる。

ssh

sshに関するところだけ

staging:
  hostname:example.com
  user: itoh
  multiplexing: true
  sshOptions:
    port: 2222
    UserKnownHostsFile: /dev/null
    StrictHostKeyChecking: no
    IdentityFile: /home/vagrant/.ssh/id_rsa_example

これで、itoh@example.com:2222に専用の/home/vagrant/.ssh/id_rsa_exampleキーで入る。その際にFingerPrintはチェックしないし保存も/dev/nullにする(保存しない)。

とりあえずDeploy

なんかあったら怖いのでteststagingって作ってみてテストしてみる。

$ ./vendor/bin/dep deploy teststaging

なるほど、デプロイサーバーからgitで取ってくる必要があるのね。

Laravelで"This action is unauthorized."と出て、Policy設定してるのに…とModelの関係を追ってみた件

あるControllerを継承した別のControllerで

<?php
        $this->authorizeResource(Order::class, 'order');

したら、Policyがスルーされてしまった。

authorizeResource()の引数は何に使われるのか?

第一引数から、Policyが設定される。上の場合だと、OrderPolicyになる。

第二引数からは、Routeから得られるパラメータを指定してる…らしい。order/show/{order}のやつね。多分。

Policyのメソッドは何が使われるのか?

単純にPOSTメソッドの場合はController::storeメソッドで、Policy::create()なんじゃないの?と思っていたが、

<?php
    public function create(User $user)
    {

だと、コールされなかった。

なぜなら、$userがnullだったから。未ログインユーザーの場合はここに到達できず、ということはメソッド自体が無くUnauthorizeとなる。

ということで、$userがnullの場合の解決法

<?php
    public function create(User $user = null)
    {

とする。

これでnullの場合もこのPolicyのcreate()が使われる。

しかしまぁ、

結局、Controllerのメソッドで

<?php
    public function store(Request $request){
        $this->authorize('create_other', Preorder::class);

と明示的に指定してPolicyにcreate_otherメソッドを生やして対応した方が筋はいいのかな。

endroid/qr-codeのSVGでQRコードを作って2つ以上置くと問題が生じる件

Packagistでqrcodeと検索すると一番上に出てくるパッケージがこれ。

SVG出力もしてくれて使いやすいんだけど、SVGQRコードを2つ以上置くときに問題が発生した。

f:id:tohokuaiki:20201006190857p:plain
こんな感じになってしまう。
(左のQRコードの真ん中が黒いのはQRコードを読めないようにしただけ)

原因

原因は、1つ目のQRコード

<defs><rect id="block" width="3" height="3" fill="#000000" fill-opacity="1"/></defs>

としていて、2つ目のQRコード

<defs><rect id="block" width="6" height="6" fill="#000000" fill-opacity="1"/></defs>

としているんだけど、このrect#blockが被ってしまっているので、1つ目のwidth/heightを2つ目のQRコードにも使ってしまうのであった。

対応

コードを見たら

https://github.com/endroid/qr-code/blob/759e7745a80c17ab3b6b946550451413df0795d1/src/Writer/SvgWriter.php#L41

        $blockDefinition->addAttribute('id', 'block');

https://github.com/endroid/qr-code/blob/759e7745a80c17ab3b6b946550451413df0795d1/src/Writer/SvgWriter.php#L45

                    $block->addAttribute('xlink:href', '#block', 'http://www.w3.org/1999/xlink');

おぉっと、これはハードコーディングですね。

ということで、これは出力したSVGタグをstr_replaceで回避するしかない…プルリクエスト出しておいた。

HTMLのcanvasで任意の4点をクリックして四角形を描く

なんか、ライブラリであるかな?と思ったけど、意外と書いちゃった方が楽なんじゃないかと思って書いた。

結論から申し上げますと、外積がメッチャ役に立った。

任意の4点から四角形を描く方法

なんかこれどう考えればいいんだろう?って思ったけど、良いのがあった。

4点をpi(i=1,2,3,4)とします.

逆の発想で線分が交わるかどうかを考えます.

線分p1-p2と線分p3-p4が交わりかつ重なっていない場合には, 四角形p1 p3 p2 p4は凸四角形になります (証明は幾何学計算幾何学の本に載っていると思います).

なので線分p1-p2と線分p3-p4, 線分p1-p3とp2-p4, 線分p1-p4と線分p2-p3が交わるかどうかを調べて交わっているものがあればそれを採用し凸四角形を作ります.

アルゴリズムをまとめると

p1-p2とp3-p4の交差判定 交わったら凸四角形p1 p3 p2 p4を描く 交わらなかったら次へ p1-p3とp2-p4の交差判定 交わったら凸四角形p1 p2 p3 p4を描く 交わらなかったら次へ p1-p4とp2-p3の交差判定 交わったら凸四角形p1 p2 p4 p3を描く 交わらなかったら凸四角形が作れないと判断する となります.

おー。なるほど。四角形なら高々3回の判定でできるわけだ。

…で、2つの線分が交点を作るかどうか…って。

2つの線分が交点を作るかどうかの判定

これも良いのがあった。

これを判定部分にすると入力値の4点pに対して四角形を描くのに都合の良い配列sが得られる。

というわけで、これを合体させて

こんな感じ。

p = [[79, 30], [283, 44], [19, 228], [325, 219]];

let is_cross = function(line1, line2) {
    var l1_from  = line1[0];
    var l1_to    = line1[1];
    var l2_from  = line2[0];
    var l2_to    = line2[1];
    var line_formula = function(l){
        return l[1]-l1_from[1]-(l[0]-l1_from[0])*(l1_to[1]-l1_from[1])/(l1_to[0]-l1_from[0]);
    }
    return line_formula(l2_from)*line_formula(l2_to)<0;
}
if (is_cross([p[0], p[1]] , [p[2], p[3]])){
    s = [p[0], p[2], p[1] , p[3]];
}
else if (is_cross([p[0], p[2]] , [p[1], p[3]])){
    s = [p[0], p[1], p[2] , p[3]];
}
else {
    s = [p[0], p[1], p[3] , p[2]];
}
console.table(s);
// s = [[79, 30], [19, 228], [325, 219], [283, 44]]

HTMLで書くとこんな感じ。

コピペで動くコードだとこんな。

<html>
<head>
<script>
var points = [];
drawSquare = function(e) {
    let cvs = document.getElementById('c');
    let ctx = cvs.getContext('2d');
    points.push([e.offsetX, e.offsetY]);
    if (points.length > 4){
        points.shift();
    }
    ctx.clearRect(0, 0, cvs.width, cvs.height);
    ctx.strokeStyle = "#14ff10";
    ctx.fillStyle   = "#ff6b6b";
    ctx.lineWidth   = 2;
    for (let i in points){
        ctx.beginPath();
        ctx.arc(points[i][0], points[i][1], 3, 0, Math.PI*2, true);
        ctx.fill();
    }
    if (points.length === 4) {
        // draw square
        let p = points.slice(0, points.length);
        let s = [];
        let is_cross = function(line1, line2) {
            var l1_from  = line1[0];
            var l1_to    = line1[1];
            var l2_from  = line2[0];
            var l2_to    = line2[1];
            var line_formula = function(l){
                return l[1]-l1_from[1]-(l[0]-l1_from[0])*(l1_to[1]-l1_from[1])/(l1_to[0]-l1_from[0]);
            }
            return line_formula(l2_from)*line_formula(l2_to)<0;
        }
        if (is_cross([p[0], p[1]] , [p[2], p[3]])){
            s = [p[0], p[2], p[1] , p[3]];
        }
        else if (is_cross([p[0], p[2]] , [p[1], p[3]])){
            s = [p[0], p[1], p[2] , p[3]];
        }
        else {
            s = [p[0], p[1], p[3] , p[2]];
        }
        // draw lines
        for (let i=0,j=s.length; i<j; i++){
            ctx.beginPath();
            ctx.moveTo(s[i][0] , s[i][1]);
            let k = (i === s.length -1 ? 0 : i + 1);
            ctx.lineTo(s[k][0] , s[k][1]);
            ctx.stroke();
        }
    }
}
</script>
</head>
<body>
<canvas width="400" height="400" style="border:1px solid #ccc; margin: 20px;" id="c" onclick="drawSquare(event)"></canvas>
</body>
</html>

更に、時計回りか反時計回りか分かるように判定ロジックを入れて確認できるようにした。

<html>
<head>
<script>
var drawVerticalLineAnim = function(p1, p2) {
    let count = 50;
    let counter = 0;
    let cvs = document.getElementById('c');
    let ctx = cvs.getContext('2d');

    var render = function() {
        counter++;
        let next = [
           Math.round( (p2[0] - p1[0])/count * counter ) + p1[0],
           Math.round( (p2[1] - p1[1])/count * counter ) + p1[1]
            ];
        ctx.beginPath();
        ctx.moveTo(p1[0], p1[1]);
        ctx.lineTo(next[0], next[1]);
        ctx.closePath();
        ctx.stroke();

        // 描画を繰り返す条件
        if (counter <= count ){
            requestAnimationFrame(render);
        }
      
    };
    render();
};

var points = [];
drawSquare = function(e) {
    let cvs = document.getElementById('c');
    let ctx = cvs.getContext('2d');
    if (points.length === 4){
        points = [];
    }
    points.push([e.offsetX, e.offsetY]);
    ctx.clearRect(0, 0, cvs.width, cvs.height);
    ctx.strokeStyle = "#14ff10";
    ctx.fillStyle   = "#ff6b6b";
    ctx.lineWidth   = 2;
    for (let i in points){
        ctx.beginPath();
        ctx.arc(points[i][0], points[i][1], 3, 0, Math.PI*2, true);
        ctx.fill();
    }
    if (points.length === 4) {
        // draw square
        let p = points.slice(0, points.length);
        let s = [];
        let is_cross = function(line1, line2) {
            var l1_from  = line1[0];
            var l1_to    = line1[1];
            var l2_from  = line2[0];
            var l2_to    = line2[1];
            var line_formula = function(l){
                return l[1]-l1_from[1]-(l[0]-l1_from[0])*(l1_to[1]-l1_from[1])/(l1_to[0]-l1_from[0]);
            }
            return line_formula(l2_from)*line_formula(l2_to)<0;
        }
        if (is_cross([p[0], p[1]] , [p[2], p[3]])){
            s = [p[0], p[2], p[1] , p[3]];
        }
        else if (is_cross([p[0], p[2]] , [p[1], p[3]])){
            s = [p[0], p[1], p[2] , p[3]];
        }
        else {
            s = [p[0], p[1], p[3] , p[2]];
        }
        // draw lines
        for (let i=0,j=s.length; i<j; i++){
            let k = (i === s.length -1 ? 0 : i + 1);
            drawVerticalLineAnim(s[i], s[k]);
            /*
            ctx.beginPath();
            ctx.moveTo(s[i][0] , s[i][1]);
            let k = (i === s.length -1 ? 0 : i + 1);
            ctx.lineTo(s[k][0] , s[k][1]);
            ctx.stroke();
            */
        }
        checkDirection(s);
    }
}
var checkDirection = function(s){
    let anti = '反';
    
    let S = 0;
    for (let i=0,j=s.length; i<j; i++){
        let a = s[i];
        let b = i == j-1 ? s[0] : s[i+1];
        S += a[0] * b[1] - b[0] * a[1];
    }
    console.log('---'); 
    if (S > 0) {
        anti =  '';
    }
    document.getElementById('direction').innerText = anti + '時計回り';
};
</script>
</head>
<body>
<p id="direction"></p>
<canvas width="400" height="400" style="border:1px solid #ccc; margin: 20px;" id="c" onclick="drawSquare(event)"></canvas>
</body>
</html>

時計回りか反時計回りかの判定ロジック

外積を使うとできる。 外積なんて20年振りに効いたわ。

当たり前だけど、多角形の場合にも使える。

外積は符号に「右向き」「左向き」の意味を持つのでこの外積の符号で判定する。

ベクトルABから見て・・・ACとの外積とADとの外積の2つがどっちとも正なら同じ向きにあるので交わらない。外積の符号が逆ならどっちかが反対回りなので交わっている。文で書くと理解できないけど、図にするとすぐわかる。自分で図を書くとすぐわかるかな。

f:id:tohokuaiki:20201006131202p:plain
交わらない場合の外積

f:id:tohokuaiki:20201009150735p:plain
交わる場合の外積

交差する判定も、外積で行った

<html>
<head>
<script>
var drawVerticalLineAnim = function(p1, p2) {
    let count = 50;
    let counter = 0;
    let cvs = document.getElementById('c');
    let ctx = cvs.getContext('2d');

    var render = function() {
        counter++;
        let next = [
           Math.round( (p2[0] - p1[0])/count * counter ) + p1[0],
           Math.round( (p2[1] - p1[1])/count * counter ) + p1[1]
            ];
        ctx.beginPath();
        ctx.moveTo(p1[0], p1[1]);
        ctx.lineTo(next[0], next[1]);
        ctx.closePath();
        ctx.stroke();

        // 描画を繰り返す条件
        if (counter <= count ){
            requestAnimationFrame(render);
        }
      
    };
    render();
};

var points = [];
drawSquare = function(e) {
    let cvs = document.getElementById('c');
    let ctx = cvs.getContext('2d');
    if (points.length === 4){
        points = [];
    }
    points.push([e.offsetX, e.offsetY]);
    ctx.clearRect(0, 0, cvs.width, cvs.height);
    ctx.strokeStyle = "#14ff10";
    ctx.fillStyle   = "#ff6b6b";
    ctx.lineWidth   = 2;
    for (let i in points){
        ctx.beginPath();
        ctx.arc(points[i][0], points[i][1], 3, 0, Math.PI*2, true);
        ctx.fill();
    }
    if (points.length === 4) {
        // draw square
        let p = points.slice(0, points.length);
        let s = [];
        /*
        // 線分の式を用いる方法
        let is_cross = function(line1, line2) {
            var l1_from  = line1[0];
            var l1_to    = line1[1];
            var l2_from  = line2[0];
            var l2_to    = line2[1];
            var line_formula = function(l){
                return l[1]-l1_from[1]-(l[0]-l1_from[0])*(l1_to[1]-l1_from[1])/(l1_to[0]-l1_from[0]);
            }
            return line_formula(l2_from)*line_formula(l2_to)<0;
        }
        */
        // 外積を用いる方法
        let is_cross = function(line1, line2){
            var a = line1[0]; // A
            var b = line1[1]; // B
            var c = line2[0]; // C
            var d = line2[1]; // D
            let s = (b[0] - a[0]) * (c[1] - a[1]) - (c[0] - a[0]) * (b[1] - a[1]);
            let t = (b[0] - a[0]) * (d[1] - a[1]) - (d[0] - a[0]) * (b[1] - a[1]);
            return s * t < 0 ;
        }
        if (is_cross([p[0], p[1]] , [p[2], p[3]])){
            s = [p[0], p[2], p[1] , p[3]];
        }
        else if (is_cross([p[0], p[2]] , [p[1], p[3]])){
            s = [p[0], p[1], p[2] , p[3]];
        }
        else {
            s = [p[0], p[1], p[3] , p[2]];
        }
        // draw lines
        for (let i=0,j=s.length; i<j; i++){
            let k = (i === s.length -1 ? 0 : i + 1);
            drawVerticalLineAnim(s[i], s[k]);
            /*
            ctx.beginPath();
            ctx.moveTo(s[i][0] , s[i][1]);
            let k = (i === s.length -1 ? 0 : i + 1);
            ctx.lineTo(s[k][0] , s[k][1]);
            ctx.stroke();
            */
        }
        checkDirection(s);
    }
}
var checkDirection = function(s){
    let anti = '反';
    
    let S = 0;
    for (let i=0,j=s.length; i<j; i++){
        let a = s[i];
        let b = i == j-1 ? s[0] : s[i+1];
        S += a[0] * b[1] - b[0] * a[1];
    }
    console.log('---'); 
    if (S > 0) {
        anti =  '';
    }
    document.getElementById('direction').innerText = anti + '時計回り';
};
</script>
</head>
<body>
<p id="direction"></p>
<canvas width="400" height="400" style="border:1px solid #ccc; margin: 20px;" id="c" onclick="drawSquare(event)"></canvas>
<script>
window.onload = function(){
//    drawVerticalLineAnim([10, 10], [100, 100]);
};
</script>
</body>
</html>

先ほどの判定だとzero divが起こってしまう可能性があったので。

Pythonで画像のトリミングをいい感じでやってくれる(CentOS8)

この記事を見てやってみようと思いました。

環境

こんな感じ。

[root@centos8 ~]# cat /etc/redhat-release
CentOS Linux release 8.2.2004 (Core)
[root@centos8 ~]# which python3.6
/usr/bin/python3.6
[root@centos8 ~]# which pip3.6
/usr/local/bin/pip3.6

とりあえず、環境設定

[vagrant@centos8 ~]$ pip3.6 install opencv-python
Collecting opencv-python
  Downloading https://files.pythonhosted.org/packages/38/a9/cd39fd25df434b5d9451dc266c12b72f68282a2b9bd5d7b4aa2d57d6c20e/opencv-python-4.4.0.44.tar.gz (88.9MB)
    100% |=====================| 88.9MB 16kB/s
    Complete output from command python setup.py egg_info:
    Traceback (most recent call last):
      File "<string>", line 1, in <module>
      File "/tmp/pip-build-85w38wrx/opencv-python/setup.py", line 9, in <module>
        import skbuild
    ModuleNotFoundError: No module named 'skbuild'

    ----------------------------------------
Command "python setup.py egg_info" failed with error code 1 in /tmp/pip-build-85w38wrx/opencv-python/

となってコケる… こちらを見るとpip自体が古いみたい。

$ sudo su -
[root@centos8 ~]# pip3.6 install -U pip
WARNING: Running pip install with root privileges is generally not a good idea. Try `pip3.6 install --user` instead.
Collecting pip
  Downloading https://files.pythonhosted.org/packages/4e/5f/528232275f6509b1fff703c9280e58951a81abe24640905de621c9f81839/pip-20.2.3-py2.py3-none-any.whl (1.5MB)
    100% |=====================| 1.5MB 904kB/s
Installing collected packages: pip
Successfully installed pip-20.2.3

[root@centos8 ~]# logout
[vagrant@centos8 ~]$ pip3.6 install opencv-python
WARNING: pip is being invoked by an old script wrapper. This will fail in a future version of pip.
Please see https://github.com/pypa/pip/issues/5599 for advice on fixing the underlying issue.
To avoid this problem you can invoke Python with '-m pip' instead of running pip directly.
Defaulting to user installation because normal site-packages is not writeable
Collecting opencv-python
  Downloading opencv_python-4.4.0.44-cp36-cp36m-manylinux2014_x86_64.whl (49.5 MB)
     |=====================| 49.5 MB 498 kB/s
Collecting numpy>=1.13.3
  Downloading numpy-1.19.2-cp36-cp36m-manylinux2010_x86_64.whl (14.5 MB)
     |=====================| 14.5 MB 12.9 MB/s
Installing collected packages: numpy, opencv-python
Successfully installed numpy-1.19.2 opencv-python-4.4.0.44

インストールできた。

プログラムを動かしてみる

Pythonはほとんどやったことありません。

とりあえず、コピペしてみる。

import cv2
import numpy as np

from IPython.display import display, Image

def display_cv_image(image, format='.png'):
    decoded_bytes = cv2.imencode(format, image)[1].tobytes()
    display(Image(data=decoded_bytes))

img = cv2.imread("適当な画像.jpg")
display_cv_image(img)

# グレイスケール化
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# 二値化
ret,th1 = cv2.threshold(gray,200,255,cv2.THRESH_BINARY)
display_cv_image(th1)

おもむろに、

$ python3.6 opencv_test.py

してみると

ModuleNotFoundError: No module named 'IPython'

なるほど…ipython(全く知らない)が必要っぽいな…ということで

$ pip3.6 install ipython

すると、何やらインストールした。この後で $ python3.6 opencv_test.py すると動いた。

display_cv_image()ってなんやねん…

これ、バイナリを出力してくれるのかと思ったけど、なんか違う。リダイレクトさせても

<IPython.core.display.Image object>

としか出ない…うーん。

とりあえずファイルに出力する

display_cv_imageというのがイマイチ良くわからないけど、とりあえずこんな感じでファイルに出力するだけにする。

def display_cv_image(image, output='output.jpg'):
     cv2.imwrite(output, image)

先ほどのエントリのコードを

import cv2
import numpy as np

from IPython.display import display, Image

def display_cv_image(image, output='output.jpg'):
     cv2.imwrite(output, image)
#    decoded_bytes = cv2.imencode(format, image)[1].tobytes()
#    display(Image(data=decoded_bytes))

img = cv2.imread("IMG_8270.jpg")
# display_cv_image(img)

# グレイスケール化
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# 二値化
ret,th1 = cv2.threshold(gray,200,255,cv2.THRESH_BINARY)
display_cv_image(th1)

すると動いた。やった。

輪郭検出

エントリの

# 二値化
ret,th1 = cv2.threshold(gray,200,255,cv2.THRESH_BINARY)
display_cv_image(th1)

image, contours, hierarchy = cv2.findContours(th1, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

# 面積の大きいもののみ選別
areas = []
for cnt in contours:
    area = cv2.contourArea(cnt)
    if area > 10000:
        epsilon = 0.1*cv2.arcLength(cnt,True)
        approx = cv2.approxPolyDP(cnt,epsilon,True)
        areas.append(approx)

cv2.drawContours(img,areas,-1,(0,255,0),3)
display_cv_image(img)

をすると、エラーが出た。

got 2)
[vagrant@centos8 opencv]$ python3.6 trimming.py
Traceback (most recent call last):
  File "trimming.py", line 22, in <module>
    image, contours, hierarchy = cv2.findContours(th1, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
ValueError: not enough values to unpack (expected 3, got 2)

にある「opencvの最新版であるOpenCV4では、」ということで

[vagrant@centos8 opencv]$ pip3.6 show opencv-python
WARNING: pip is being invoked by an old script wrapper. This will fail in a future version of pip.
Please see https://github.com/pypa/pip/issues/5599 for advice on fixing the underlying issue.
To avoid this problem you can invoke Python with '-m pip' instead of running pip directly.
Name: opencv-python
Version: 4.4.0.44
Summary: Wrapper package for OpenCV python bindings.
Home-page: https://github.com/skvark/opencv-python
Author: None
Author-email: None
License: MIT
Location: /home/vagrant/.local/lib/python3.6/site-packages
Requires: numpy
Required-by:

4.4.0.44だった。ということで、

image, contours, hierarchy = cv2.findContours(th1, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

contours, hierarchy = cv2.findContours(th1, cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)

にして動いた。