tohokuaikiのチラシの裏

技術的ネタとか。

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)

にして動いた。

laravel-mixでnpm run watchしたら無限ループに陥ったので対処したこと

何気なくsassを書き直したら、npm run watchしてるのが無限ループしてた…

原因は画像のタイムスタンプ?

原因を探ってみると、

header {
    background-image: url("../images/icon_header.png");
}

が原因っぽい。

試しに、npm run prodしてみると、無事にコンパイルされて

  DONE  Compiled successfully in 24206ms                                                                   2:45:32 AM 

                                                    Asset       Size  Chunks                    Chunk Names 
                                             /css/app.css  724 bytes       1  [emitted]         /js/user
  images/icon_header.png?5154d06ae582f6e2548c6d920674da59   1.04 KiB          [emitted] 

とか出る。images/icon_header.png のタイムスタンプ見ると

なるほど、sassコンパイルする時に画像も変更するらしい。変更するんだけど、

$ md5sum ~/resources/images/icon_header.png ~/public/images/icon_header.png
5154d06ae582f6e2548c6d920674da59  ~/resources/images/icon_header.png
5154d06ae582f6e2548c6d920674da59  ~/public/images/icon_header.png

という感じでファイル自体は変更してなさそう。

対応方法

この機能をOFFにしてしまえばいいっぽい。

直接的な対応方法

laravel-mixのオプションでこの機能をOFFにするには

mix.options({
  processCssUrls: false
})

とする。

…なんだけど、これをOFFにするのはrun prodする時だけで良いので

const is_prod = mix.inProduction() ? true : false;
mix.webpackConfig({
  devtool: is_prod ? "" : "inline-source-map",
  resolve: {
    extensions: ['.js', '.vue', '.json'],
    alias: {
      'vue$': 'vue/dist/vue.esm.js',
      '@': __dirname + '/resources/js'
    }
  },
}).options({
  processCssUrls: is_prod
})
.js('resources/js/app.js', 'public/js')
.sass('resources/sass/app.scss', 'public/css/')

…ということで、watchしなければ問題ないので、productionコンパイルの時だけは聞くようにした

Laravelのview(bladeテンプレート)からVue.jsにグローバルパラメータを渡したい時など

最初、

<router-view app_name="{{ config('app.name') }}"></router-view>

とかしてたけど、こんなの逐一やってらんねー。

検索したけどあんまり見つからない…ajaxで渡せとかそんな面倒なこと…

ということで、「ぼくのかんがえるさいきょうのLaravelからVueへの変数の渡し方」

Laravel 側

Controllerで変数を渡す

<?php
    /**
     * @brief vue用のViewを返す
     */
    public function vue()
    {
        $params = [
            'app_name' => config('app.name')
        ];
        return view('index')
            ->with('params', $params);
    }

bladeでJavaScriptグローバル変数に振ってやる

@section('content')
<div id="app">
    <router-view></router-view>
</div>
<script>var globalParams = @json($params);</script>
<script src="{{ asset(mix('js/app.js')) }}" defer></script>
@endsection

Vue側

Vueのrootにcomputedにgparamをつける

import Vue from 'vue';
// 省略
const app = new Vue({
    el: '#app',
    // 省略
    computed: {
        gparam: function(){
            return globalParams;
        }
    },

Vueの各Vueやコンポーネントでは$rootでcallする。

<template>
<div>
     <h1>{{ $root.gparam.app_name }}</h1>

ドヤァ

PhpStormでXdebugがつながってるのにブレークポイントで止まってくれない時

./artisan serveだと止まってくれるのにApacheだとブレークポイントで止まってくれないなぜだ!!!???

とずっと思ってました。

色々と困ってる人がいました。
Googleで「file path is not mapped to any file path on server」とか検索すると

Debug problems: "remote file path ... is not mapped to any file path in project" – IDEs Support (IntelliJ Platform) | JetBrains
PHP(xdebug)+IntelliJでブレークポイント置いてるのに、そこで止まってくれないとき - カイワレの大冒険 Third
PhpStorm throws an error "Remote file path ... is not mapped to any file path in project" in the client-to-API context – IDEs Support (IntelliJ Platform) | JetBrains

で、ようやくわかりました。

ファイルのマッピングがうまくいってないんです。

artisan serveの場合は、相対パスで上手く決めてくれるっぽいんですが、Apacheだとそうはいきませんよね。

なので、ネットワークドライブに割り当てた後、こういう感じでProjectFilesにバーチャルパスをマッピングするとうまくいきました。

f:id:tohokuaiki:20200916152151p:plain

あー。こんなことで…

MySQLのUNIQUE制約のキー長制限に引っかかった

LaravelのユーザーテーブルのEmailのUNIQUEが掛けられなかった。

Server version: 5.5.65-MariaDB MariaDB Server

SQLSTATE[42000]: Syntax error or access violation: 1071 Specified key was too long; max key length is 767 bytes (SQL: alter table `users` add unique `users_email_unique`(`email`))

maxで767Byteなんだけど、…ってことは、UTF8が1文字4byteで計算されてるのかな?

767÷4=191.75なので…191文字までなら大丈夫なのかな?

ということで実験

VARCHAR(192)にしてみる

MariaDB [foodb]> alter table users change email email varchar(192) not null;
Query OK, 0 rows affected (0.00 sec)
Records: 0  Duplicates: 0  Warnings: 0

MariaDB [foodb]> alter table `users` add unique `users_email_unique`(`email`);
ERROR 1071 (42000): Specified key was too long; max key length is 767 bytes

ダメだって…

VARCHAR(191)にしてみる

MariaDB [foodb]> alter table users change email email varchar(191) not null;
Query OK, 0 rows affected (0.01 sec)
Records: 0  Duplicates: 0  Warnings: 0

MariaDB [foodb]> alter table `users` add unique `users_email_unique`(`email`);
Query OK, 0 rows affected (0.02 sec)
Records: 0  Duplicates: 0  Warnings: 0

計画通り(夜神ライトAA略

理由

show create table users;

してみると、

CREATE TABLE `users` (
...略...
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci

ってなってて、utf8mb4だった。ので4byteなのね。