PythonでOpenCVやnumpyを使って画像を加工するフィルターいろいろ

python コンピュータ
python

OpenCVには多数の画像フィルターがあります。また、numpyを使うと画像をピクセル単位で加工するフィルターを作成することが出来ます。個人的によく使う画像フィルター類をまとめたいと思います。

ライブラリのインポート

import cv2
import numpy as np
import os

osは画像加工には直接関係ないですが、画像ファイルの読み書きのパスの生成やファイルの有無の確認、ディレクトリの作成用

画像ファイルの読み込み

カラー読み込み

img = cv2.imread("F:/tmp/hoge.jpg",1)

グレースケールで読み込み

img = cv2.imread("F:/tmp/hoge.jpg",0)

パスに日本語?含むファイルは読み込めない。

画像ファイルの書き込み

cv2.imwrite("F:/tmp/hoge.jpg", img)

画像ファイルの書き込み

cv2.imwrite("F:/tmp/hoge.jpg", img)

画像の表示

cv2.imshow('img', img)
cv2.waitKey(0)
cv2.destroyAllWindows()

CLI環境でウィンドウで画像を表示します。
任意のキーを押されるまで待ち、キーが押されるとウィンドウを破棄します。

2値化

result = np.where(gray < 128, np.uint8(0), np.uint8(255))

numpyのwhereを使って128をしきい値に0か255に分類する単純な方法

大須の2値化

ret,result = cv2.threshold(gray, 0, 255, cv2.THRESH_OTSU)

適切なしきい値が自動的に設定されます。引数の閾値(0)は使われない模様。

グレースケール化

gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

imreadでカラーとして読み込んだ画像(BGR)をグレースケールへ変換

RGB[A] to Gray:Y←0.299⋅R+0.587⋅G+0.114⋅B

緑成分強め

ぼかし処理

result = cv2.blur(img, (3,3))

カーネルサイズは奇数

ガウシアンフィルター

result = cv2.GaussianBlur(img, (3,3), 0)

メディアンフィルター

result = cv2.medianBlur(gray, 3)

バイラテラルフィルター

result = cv2.bilateralFilter(gray, 3, sigmaColor=999, sigmaSpace=20)

ノンローカルミーンフィルター(グレースケール)

result = cv2.fastNlMeansDenoising(gray, h=15)

アンシャープマスキングフィルター

def unsharp(src, k=1.5):
    kernel = np.array( [[-k/9.0, -k/9.0, -k/9.0],
                        [-k/9.0, 1.0+(8.0*k)/9.0, -k/9.0],
                        [-k/9.0, -k/9.0, -k/9.0]])
    dst = cv2.filter2D(src, -1, kernel)
    return dst

ガンマ補正

gamma = 0.9
x = np.arange(256)
y = (x / 255) ** gamma * 255    
result = np.uint8(cv2.LUT(gray, y))

ソーベルフィルター

sobel_x = cv2.Sobel(gray, cv2.CV_32F, 1, 0, ksize=3)
sobel_y = cv2.Sobel(gray, cv2.CV_32F, 0, 1, ksize=3)
sobel_x = cv2.convertScaleAbs(sobel_x)
sobel_y = cv2.convertScaleAbs(sobel_y)
result = cv2.addWeighted(sobel_x, 0.5, sobel_y, 0.5, 0)

指定面積以下の物体を塗りつぶし

def fc(gray, am=9):
    th=cv2.adaptiveThreshold(gray, 255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY,11,2)
    contours,hierarchy = cv2.findContours(th,  cv2.RETR_EXTERNAL | cv2.RETR_TREE  , cv2.CHAIN_APPROX_NONE)
    new_contours=[]
    for c in contours:
        s=abs(cv2.contourArea(c))
        if s <= am:
            new_contours.append(c)

    cv2.drawContours(gray, new_contours, -1, 255, -1)    
    return gray

リサイズ

scale=4.0
result = cv2.resize(img, None, fx=scale, fy=scale, interpolation=cv2.INTER_LINEAR)

interpolation(補完方法)
cv2.INTER_NEAREST
cv2.INTER_LINEAR
cv2.INTER_AREA
cv2.INTER_CUBIC
cv2.INTER_LANCZOS4

画像の重ね合わせ

result = cv2.addWeighted(img1, 0.5, img2, 0.5,  0.0)

画像の減算

result = cv2.subtract(img1, img2)

ネガポジ反転

result = cv2.bitwise_not(img)

畳み込み演算による平滑化(ぼかし処理)

def avgBlur(src, ksize=3):
    d = int((ksize-1)/2)
    dst = src.copy()
    (h, w) = src.shape[:2]
    for y in range(d, h-d):
        for x in range(d, w-d):
            a = np.ravel(src[y-d:y+d+1, x-d:x+d+1])
            dst[y][x] = np.mean(a)
    return dst 

OpenCVのfilter2Dメソッドと同じような動作をするフィルターをnumpyで記述しています。
サンプルでは平均をとるblurなのでnp.meanを使っていますが、カーネルを引数に取るようにすると、よりfilter2Dのような形になります。filter2dと比べて動作は遅いです。畳み込み演算の範囲外(外周部)はオリジナル画像のコピーをセットしています。

任意の解像度のグレスケール画像を生成

width = 640
height = 480
img=np.zeros((height, width), np.uint8)

任意の解像度のカラー画像えを生成

width = 640
height = 480
channel = 3
img=np.zeros((height, width, channel), np.uint8)

座標を指定してピクセルにアクセス

カラー

x = 1
y = 0
img[y][x] = [255,255,255]
x = 1
y = 0
img[y][x] = 255

座標を指定して特定のチャンネルにアクセス

x = 0
y = 0
img[y,x,0] = 255

座標x=0:y=0の0(B…青)番目のチャンネルに255を代入

特定のチャンネルを塗りつぶし


img[:,:,2] = 255

範囲指定で塗りつぶし


img[1:3,1:3] = [0, 255, 0]

x座標1~2y座標1~2の範囲を緑色で塗りつぶす。

numpy.where()による2値化


th = np.where(gray < 128, np.uint8(0), np.uint8(255)

grayの要素全てに対し128より小さい場合は0(黒)それ以外は255(白)で生成した画像配列をthに返す。

幅、高さ、チャンネル数を取得

h, w, channel= img.shape

LUTによる減色

def gensyoku(img, k=8):
    LUT = []
    width = 256/k # 32.0
    center = 256/k/2 # 16.0
    for i in range(k):  # i:0~7
        value = center + (width*i) # 置き換える値を算出
        ave_list = [value]*int(width) # 同じ色の幅ぶん要素の配列
        LUT = LUT+ave_list # 配列に追加
    LUTN = np.array(LUT, dtype=np.uint8) # 配列をndarrayに変換
    return cv2.LUT(img, LUTN)

グレースケール同士の2つの画像の違いを抽出

diff = np.where(img_a != img_b, np.uint8(255), np.uint8(0))

img_aとimg_bが同じ部分は黒、異なる部分が白の2値画像が出来上がる。

そのた

import cv2
import numpy as np
import os

# 減色
def gensyoku(img, k=8):
    LUT = []
    width = 256/k # 32.0
    center = 256/k/2 # 16.0
    for i in range(k):  # i:0~7
        value = center + (width*i) # 置き換える値を算出
        ave_list = [value]*int(width) # 同じ色の幅ぶん要素の配列
        LUT = LUT+ave_list # 配列に追加
    LUTN = np.array(LUT, dtype=np.uint8) # 配列をndarrayに変換
    return cv2.LUT(img, LUTN)

# ガンマ補正
def gamma(img, g=0.9):
    x = np.arange(256)
    y = (x / 255) ** g * 255
    return np.uint8(cv2.LUT(img, y))    

# 十字
def cross(src):
    kernel = np.array( [[0.0/8.0, 1.0/8.0, 0.0/8.0],
                        [1.0/8.0, 4.0/8.0, 1.0/8.0],
                        [0.0/8.0, 1.0/8.0, 0.0/8.0]])
    dst = cv2.filter2D(src, -1, kernel)
    return dst    

def filter(src):
    dst = src.copy()
    # ぼかし
    for i in range(3):
        dst = cross(dst)
        #dst = cv2.GaussianBlur(dst, (3,3), 0)
    # ガンマ補正
    dst = gamma(dst, 1.1)
    # ノンローカルミーン
    dst = cv2.fastNlMeansDenoising(dst, h=5)

    return dst

if __name__ == '__main__':
    input_path = 'input/005.jpg'
    output_path = 'outdir/005.png'
    
    img = cv2.imread(input_path,0)

    dst = filter(img)

    cv2.imwrite(output_path, dst)

コメント