にせねこメモ

はてなダイアリーがUTF-8じゃないので移ってきました。

Python 3 から Potrace を使う

Python 3.5 から Potrace を使い、ラスタ画像をベクタに変換したい。
より詳細に言うと、 OpenCV 3 の Python Bindings の cv2.imread() で読み込んだ画像データ(numpy.ndarray)をSVGデータに変換したかった。


pypotrace ってのもあるけど 3.5 で使えるのか分からなかったので見送り。


python から potrace 使ってる例がないかと探してみたらあった。

ここではパイプで標準入力から入力し、パイプで標準出力に出力している。
今回はこのサイトのやり方を元にした。

実装

Potrace は、標準入力からファイルを受け取ると、出力を標準出力に吐くとのことである。

If no filename arguments are given, then potrace acts as a filter, reading from standard input and writing to standard output. A filename of "-" may be given to specify reading from standard input.

Man page for potrace(1)

明示的に標準入力から読み込む場合はファイル名に "-" を、標準出力に出力する場合は "-o-" を指定する。


さて、 potrace が入力を受け付けるのは pnm (pbm, pgm, ppm), bmp だけである。今回は bmp を使う。
まずは cv2.imread() で読み込んだ画像データを bmp のバイナリデータにする。 cv2.imencode() は numpy.ndarray() を返すので .toarray() で bytes 型に変換している。

pic = cv2.imread('test.png', cv2.IMREAD_GRAYSCALE)

retval, buf = cv2.imencode('.bmp', image)
binbmp = buf.tobytes()

subprocess.Popen クラスでパイプを作成し、 .communicate() メソッドに input を指定して potrace の標準出力にBMPのバイナリを流し込む。問題がなければ stdout に potrace から出力されたSVGのbytesデータが入る。

p = subprocess.Popen(
    ['potrace', '-', '-o-', '--svg'],
    stdin=subprocess.PIPE,
    stdout=subprocess.PIPE,
    stderr=subprocess.PIPE)
stdout, stderr = p.communicate(input=binbmp)

コード

こんな感じになった。

#!/usr/bin/env/python3
# coding: utf-8

import numpy as np, cv2, subprocess

# potrace command
POTRACE = 'potrace'
    
def passpotrace(image): 
    # convert to bmp binary so that potrace can handle it
    retval, buf = cv2.imencode('.bmp', image)
    if retval == False:
        raise ValueError('Failed to convert into BMP binary data')
    # convert buf from numpy.ndarray to bytes
    binbmp = buf.tobytes()
    
    args = [
        POTRACE,
        '-', '-o-', '--svg'
    ]
    
    p = subprocess.Popen(
        args,
        stdin=subprocess.PIPE,
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
        shell=False
        )
        
    stdout, stderr = p.communicate(input=binbmp)
    if len(stderr) != 0:
        raise RuntimeError('Potrace threw error:\n' + stderr.decode('utf-8'))

    return stdout
    
if __name__ == '__main__':
    testpic = cv2.imread('test.png', cv2.IMREAD_GRAYSCALE)
    print(passpotrace(testpic).decode('utf-8'))