エックスサーバーにOpenCVをインストールして画像処理APIを作るまで

2021年9月28日

OpenCVをエックスサーバーで動かしたい

エックスサーバーへPythonとOpenCV+falconを導入して、画像処理APIを作ろうと思いました。

OpenCVをエックスサーバーのサーバー上で動かす方法を、自分が探した限りでは、2021/09現在有効なWeb上に正しい情報がないと思います(コマンドラインから動作させる方法は見つかりますが)。そこで、まとめてみました。

まず、pipインストールをできるようにします。この辺でpipは、情報は色々転がっていますが、情報が古く、使えないパターンが多いです。

エックスサーバーではルート権限を持っていないので、普通にインストールすることはできません。解決法は、おおむね仮想環境をインストールして、そこにPythonをインストールのうえ、pipインストールするか、予めインストールされているPythonの仮想環境を使用するかのいずれかです。

ただ、予めインストールされているPythonの仮想環境では、Pythonのバージョンがエックスサーバーであらかじめインストールされているものに固定されるので、よろしくありません。また、ライブラリインストール時にエラーが出ます。

仮想環境をインストールする方法を調べてみたところ、linuxbrewを使うかanacondaを使う方法があるのですが、linuxbrewを使用する方法はlinuxbrewにアップデートが入ったようで、情報が古くなっており、導入できません。

ですので、Anacondaを導入します。

Anacondaダウンロード

以下のリンクより、Linux用インストーラを取得し、ダウンロードします。

https://www.anaconda.com/products/individual

Windowsでアクセスしているからか、ページ上部のダウンロードボタンがWindows用となっています。ページ下部にLinux用のインストーラがありますので、こちらをダウンロードしましょう。

そして、エックスサーバーにFTPやファイル管理パネルでアップロードします。

SSH接続

エックスサーバーにSSH接続します。TeraTermなどクライアントソフトをインストールし、以下のエックスサーバーのマニュアルに従って接続しましょう。

https://www.xserver.ne.jp/manual/man_server_ssh.php

Anacondaインストール

アップロードした.shファイルをshコマンドで実行すればよいです。

途中色々聞かれますが、言われた通り突き進みましょう。

以下はENTERを押すと規約が出てくるので、進める。

[xs******@sv12013 ~]$ sh Anaconda3-2021.05-Linux-x86_64.sh

Welcome to Anaconda3 2021.05

In order to continue the installation process, please review the license
agreement.
Please, press ENTER to continue
>>>

以下はyesと応答。

Last updated April 5, 2021

Do you accept the license terms? [yes|no]
[no] >>>
Please answer 'yes' or 'no':'
>>> yes

以下はENTERで進める。

Anaconda3 will now be installed into this location:
/home/xs******/anaconda3

  - Press ENTER to confirm the location
  - Press CTRL-C to abort the installation
  - Or specify a different location below

インストールが終わると、以下のようなメッセージが出力される。

Thank you for installing Anaconda3!

===========================================================================

Working with Python and Jupyter notebooks is a breeze with PyCharm Pro,
designed to be used with Anaconda. Download now and have the best data
tools at your fingertips.

PyCharm Pro for Anaconda is available at: https://www.anaconda.com/pycharm

condaコマンド実行

インストールが終わったのでcondaコマンドが使える、と思って打ってみると、あれ?

[xs******@sv12013 ~]$ conda
pyenv: conda: command not found

The `conda' command exists in these Python versions:
  anaconda3-2020.11

Note: See 'pyenv help global' for tips on allowing both
      python2 and python3 to be found.

どうも、既にpyenvとかを導入していると、Anacondaと競合してしまうらしいです。

Anacondaを優先(pyenvを使わない)するために.bashrcに以下の内容を追記します。(******はサーバー番号)

export PATH="/home/xs******/anaconda3/bin/:$PATH"

設定を有効にするために、以下をコマンド入力。

[xs******@sv12013 ~]$ source .bashrc

これでcondaコマンドが有効になりました。

conda環境の起動

condaの環境をアクティベートして、conda環境に切り替えます。「CommandNotFoundError」となった場合は、conda initしてターミナルを再起動すると治るはずです。

[xs******@sv12013 ~]$ conda init

[xs******@sv12013 ~]$ conda activate
(base) [xs******@sv12013 ~]$

ちなみに、conda環境から抜けるときは、「conda deactivate」します。

(base) [xs******@sv12013 ~]$ conda deactivate

pipインストール

conda環境でpipが使えるようになったと思いますので、pipで必要なopencv-python、falconをインストールしていきます。

opencv-pythonのインストール

コマンド「pip install opencv-python」を投入し、インストール完了後「python」コマンドでpythonを対話モードで起動。「import cv2」してエラーとならないことを確認しましょう。

完了したら「exit()」で抜けます。

(base) [xs******@sv12013 ~]$ pip install opencv-python
Collecting opencv-python
Downloading opencv_python-4.5.3.56-cp38-cp38-manylinux2014_x86_64.whl (49.9 MB)
|????????????????????????????????| 49.9 MB 482 kB/s
Requirement already satisfied: numpy>=1.17.3 in ./anaconda3/lib/python3.8/site-packages (from opencv-python) (1.20.1)
Installing collected packages: opencv-python
Successfully installed opencv-python-4.5.3.56
(base) [xs******@sv12013 ~]$ python
Python 3.8.8 (default, Apr 13 2021, 19:58:26)
[GCC 7.3.0] :: Anaconda, Inc. on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import cv2
>>> exit()

falconの導入

opencv-pythonと同様。falcon-multipartもインストールする。

(base) [xs******@sv12013 ~]$ pip install falcon
Collecting falcon
Downloading falcon-3.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (9.9 MB)
|????????????????????????????????| 9.9 MB 7.8 MB/s
Installing collected packages: falcon
Successfully installed falcon-3.0.1
(base) [xs******@sv12013 ~]$ python
Python 3.8.8 (default, Apr 13 2021, 19:58:26)
[GCC 7.3.0] :: Anaconda, Inc. on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import falcon
>>> exit()
(base) [xs*******@sv12013 ~]$ pip install falcon_multipart
Collecting falcon_multipart
Using cached falcon_multipart-0.2.0-py3-none-any.whl (5.2 kB)
Requirement already satisfied: falcon>=0.3 in ./anaconda3/lib/python3.8/site-packages (from falcon_multipart) (3.0.1)
Installing collected packages: falcon-multipart
Successfully installed falcon-multipart-0.2.0

numpyのアップデート

後の話ですが、「import cv2」したところ、先へ進みませんでした。色々試行錯誤したところ依存ライブラリのnumpyのアップデートが必要だったので、ここで実行しておきます。

pip install -U numpy
Requirement already satisfied: numpy in ./anaconda3/lib/python3.8/site-packages (1.20.1)
Collecting numpy
Using cached numpy-1.21.2-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl (15.8 MB)
Installing collected packages: numpy
Attempting uninstall: numpy
Found existing installation: numpy 1.20.1
Uninstalling numpy-1.20.1:
Successfully uninstalled numpy-1.20.1
Successfully installed numpy-1.21.2

APIを作る

例として、受信した画像をcvcvtColorでグレースケール化して応答する単純なAPIを開発します。

以下の3つのファイルを作成し、同一フォルダに置きましょう。

  1. __init__.py(中身はブランク)
  2. index.cgi
  3. opencv.py
  4. .htaccess

index.cgiの内容

#! /home/xs******/anaconda3/bin/python3

import os
os.environ['OPENBLAS_NUM_THREADS'] = "1"

from wsgiref.handlers import CGIHandler
from opencv import app
CGIHandler().run(app)

1行目の「#! /home/xs******/anaconda3/bin/python3」はanacondaのpythonの位置を示します。場所が怪しいときは、condaをアクティベートした状態の端末(TeraTerm)で「which python3」とコマンド入力すると、パスが出てきます。頭の「~」は「/home/{サーバーID}」で読み替えましょう。

(base) [xs******@sv12013 ~]$ which python3
~/anaconda3/bin/python3

基本はfalconでAPIを構築するフォーマット通りですが、「os.environ['OPENBLAS_NUM_THREADS’] = “1"」という記述を加えています。opencv-pythonが依存しているnumpyはマルチスレッドで計算できるのですが、エックスサーバー環境ではマルチスレッドでは動かないので、1を指定してシングルスレッドで処理するよう制約しています。

opencv.pyの内容

POSTで画像ファイルを受け付け、cvcvtColorしてバイトデータを返します。

import sys
import traceback

try:

    import ctypes

    #ctypes.cdll.LoadLibrary('/lib64/libGLdispatch.so.0')
    #ctypes.cdll.LoadLibrary('/lib64/libGLX.so.0')
    #ctypes.cdll.LoadLibrary('/lib64/libGL.so.1')

    import cv2
    import falcon
    from falcon_multipart.middleware import MultipartMiddleware

    import tempfile
    from io import BytesIO

except Exception as e:
    #例外が発生した場合

    exception_info = traceback.format_exc()
    with open("debug.txt", mode='a') as f:
        f.write(exception_info)


class Imageproc(object):

    def __init__(self):  #初期化: インスタンス作成時に自動的に呼ばれる
        self.test = []


    def on_get(self, req, resp):
        resp.status = falcon.HTTP_200
        resp.content_type = 'text/plain'
        resp.body = "Please use POST request!"

    def on_post(self, req, resp):

        try:

            image_data = req.get_param('file').file.read() #POSTリクエストから画像取り出し。

            #テンポラリファイルに画像を書き込む。
            fp = tempfile.NamedTemporaryFile()
            fp.write(image_data)

            #opencvの形式で画像読み込み
            cv_img = cv2.imread(fp.name)

            #グレースケール変換
            gray = cv2.cvtColor(cv_img, cv2.COLOR_BGR2GRAY)

            #Jpeg形式のバイナリデータに変換
            is_success, buffer = cv2.imencode(".jpg", gray)
            io_buf = BytesIO(buffer)

            #レスポンス
            resp.status = falcon.HTTP_200
            resp.content_type = 'application/octet-stream'
            resp.data = io_buf.getvalue()

        except:
            #例外が発生した場合
            exception_info = traceback.format_exc()
            with open("debug.txt", mode='a') as f:
                f.write(exception_info)

class CORSMiddleware:
    def process_request(self, req, resp):
        resp.set_header('Access-Control-Allow-Origin', '*')

try:

    app = falcon.App(middleware=[CORSMiddleware(), MultipartMiddleware()])
    app.add_route("/", Imageproc())

except Exception as e:
    #例外が発生した場合
    exception_info = traceback.format_exc()
    with open("debug.txt", mode='a') as f:
        f.write(exception_info)


if __name__ == "__main__":

    from wsgiref import simple_server
    httpd = simple_server.make_server("127.0.0.1", 80, app)
    httpd.serve_forever()

.htaccess

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ /test/opencv_api/index.cgi/$1 [QSA,L]

以上でAPIが完成しました。

テスト

何か画像ファイルを用意し、curlでリクエスト、受信データを保存してみます。

curl -X POST -F file=@input_test.jpg https://{domain}/test/opencv_api/ -O output_test.jpg
output_test.jpgにグレースケールされたデータができれば、OKです。