web-dev-qa-db-ja.com

画像のノイズの小さな島を削除する-Python OpenCV

一部の画像から背景ノイズを除去しようとしています。これはフィルタリングされていない画像です。

フィルター処理するために、このコードを使用して、画像に残るもののマスクを生成しました。

_ element = cv2.getStructuringElement(cv2.MORPH_RECT, (2,2))
 mask = cv2.erode(mask, element, iterations = 1)
 mask = cv2.dilate(mask, element, iterations = 1)
 mask = cv2.erode(mask, element)
_

このコードを使用して、元の画像から不要なピクセルをマスクすると、次のようになります。 

ご覧のように、中央の領域の小さな点はすべて消えていますが、より高密度の領域から来る多くの小さな点も消えています。フィルタリングを減らすために、getStructuringElement()の2番目のパラメーターを(1,1)に変更しようとしましたが、これを行うと、何もフィルターされていないかのように最初の画像が表示されます。

これらの2つの極端な間にあるフィルターを適用できる方法はありますか?

さらに、getStructuringElement()が正確に何をするのかを誰かが説明できますか? 「構造化要素」とは何ですか?それは何をし、そのサイズ(2番目のパラメーター)はフィルタリングのレベルにどのように影響しますか?

29
annena

あなたの質問の多くは、形態学的画像処理がどのように機能するのかわからないという事実から生じていますが、私たちはあなたの疑問を休めることができます。構造化要素は、比較する「基本形状」として解釈できます。構造化要素の1は、この図形で見たいピクセルに対応し、0は無視したいピクセルです。長方形(MORPH_RECTで計算したように)、楕円、円形など、さまざまな形状があります。

そのため、cv2.getStructuringElementは構造化要素を返します。最初のパラメーターは希望するタイプを指定し、2番目のパラメーターは希望するサイズを指定します。あなたの場合、2 x 2の「長方形」が必要です。これは実際には正方形ですが、それで問題ありません。

もっと卑劣な意味では、構造化要素を使用して、画像の左から右、上から下にスキャンし、ピクセル周辺を取得します。各ピクセル近傍の中心は、見ている対象ピクセルに正確にあります。各ピクセル近傍のサイズは、構造化要素と同じサイズです。

侵食

侵食については、構造化要素に接触しているピクセル近傍のすべてのピクセルを調べます。 すべての非ゼロピクセルが1である構造化要素ピクセルに触れている場合、入力に対して対応する中心位置の出力ピクセルは1です。1つである構造化ピクセルにが触れないゼロでないピクセルが少なくとも1つある場合、出力は0です。

長方形の構造化要素に関しては、構造化要素内のすべてのピクセルが、ピクセル周辺の画像の非ゼロピクセルに接触していることを確認する必要があります。そうでない場合、出力は0、そうでない場合は1です。これにより、ノイズの小さなスプリアス領域が効果的に除去され、オブジェクトの領域もわずかに減少します。

長方形が大きければ大きいほど、縮小が実行されるサイズ要因。構造化要素のサイズはベースラインであり、この長方形の構造化要素よりも小さいオブジェクトは、フィルタリングされ、出力に表示されないと見なすことができます。基本的に、1 x 1の長方形の構造化要素を選択することは、ピクセルが画像内で可能な最小の情報表現であるため、その内部のすべてのピクセルに適合するため、入力画像自体と同じです。

膨張

膨張は侵食の反対です。 1である構造化要素のピクセルに接触する非ゼロピクセルが少なくとも1つある場合、出力は1です。それ以外の場合、出力は0です。これは、オブジェクト領域をわずかに拡大し、小さな島を大きくすることと考えることができます。

ここでのサイズの意味は、構造化要素が大きいほど、オブジェクトの面積が大きくなり、孤立した島が大きくなることです。


あなたがしているのは、最初に侵食に続いて膨張です。これは、opening操作と呼ばれるものです。この操作の目的は、画像内のより大きなオブジェクトの領域を維持(試行)しながら、ノイズの小さな島を除去することです。侵食はこれらの島を取り除き、膨張はより大きなオブジェクトを元のサイズに戻します。

何らかの理由でこれに続いて再び侵食しますが、それは私にはまったく理解できませんが、それでも大丈夫です。


私が個人的に行うことは、最初にclosing操作を実行することです。これは膨張とそれに続く侵食です。クローズすると、互いに近いエリアを1つのオブジェクトにグループ化できます。そのため、他の何かを行う前に、おそらく互いに結合する必要がある互いに近いいくつかの大きな領域があることがわかります。そのため、最初に閉じてからopeningを実行して、孤立したノイズのある領域を削除できるようにします。近くのピクセルと最初の構造化要素のサイズを取得したいので、最後の構造化要素のサイズlargerを作成することに注意してくださいsmaller。これにより、大きな領域を誤って削除したくない。

一度これを行うと、元の画像で余分な情報をマスクして、小さな島が消える間、大きな領域をそのまま残します。

収縮に続いて膨張、または膨張に続いて収縮を連鎖させる代わりに、 cv2.morphologyEx を使用します。ここで、フラグとしてMORPH_OPENおよびMORPH_CLOSEを指定できます。

そのため、あなたの画像がspots.pngと呼ばれると仮定して、私は個人的にこれを行います:

import cv2
import numpy as np

img = cv2.imread('spots.png')
img_bw = 255*(cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) > 5).astype('uint8')

se1 = cv2.getStructuringElement(cv2.MORPH_RECT, (5,5))
se2 = cv2.getStructuringElement(cv2.MORPH_RECT, (2,2))
mask = cv2.morphologyEx(img_bw, cv2.MORPH_CLOSE, se1)
mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, se2)

mask = np.dstack([mask, mask, mask]) / 255
out = img * mask

cv2.imshow('Output', out)
cv2.waitKey(0)
cv2.destroyAllWindows()
cv2.imwrite('output.png', out)

上記のコードは一目瞭然です。まず、画像を読み込んでから、画像をグレースケールと5の強度のしきい値に変換して、オブジェクトピクセルと見なされるもののマスクを作成します。これはかなりきれいな画像なので、5を超えるものはすべて機能しているようです。モルフォロジールーチンでは、画像をuint8に変換し、マスクを255にスケーリングする必要があります。次に、2つの構造化要素を作成します。1つは閉じ操作用の5 x 5長方形で、もう1つは2 xオープン操作の場合は2。 cv2.morphologyExを2回実行して、しきい値設定された画像の開閉操作をそれぞれ実行します。

それを行ったら、3Dマトリックスになるようにマスクをスタックし、255で除算して[0,1]のマスクになるようにし、このマスクに元の画像を掛けて元のピクセルを取得します。画像を元に戻し、マスク出力から真のオブジェクトと見なされるものを維持します。

残りは説明のためだけです。ウィンドウに画像を表示し、output.pngというファイルに画像を保存します。その目的は、この投稿で画像がどのように見えるかを示すことです。

私はこれを得る:

enter image description here

それは完璧ではないことを心に留めておいてください、しかし、それはあなたがそれを前に持っていた方法よりはるかに良いです。良い出力とみなすものを得るために、構造化要素のサイズをいじる必要がありますが、これは確かにあなたが始めるのに十分です。がんばろう!


C++バージョン

上記で作成したコードをOpenCVを使用してC++バージョンに変換する要求がいくつかありました。私はついにコードのC++バージョンの記述に取り掛かり、これはOpenCV 3.1.0でテストされました。このコードは以下にあります。ご覧のとおり、コードはPythonバージョンで見られるものと非常に似ています。ただし、元のイメージのコピーで cv::Mat::setTo を使用しました最終マスクの一部ではなかったものはすべて0に設定します。これは、Pythonで要素ごとの乗算を実行するのと同じことです。

#include <opencv2/opencv.hpp>

using namespace cv;

int main(int argc, char *argv[])
{
    // Read in the image
    Mat img = imread("spots.png", CV_LOAD_IMAGE_COLOR);

    // Convert to black and white
    Mat img_bw;
    cvtColor(img, img_bw, COLOR_BGR2GRAY);
    img_bw = img_bw > 5;

    // Define the structuring elements
    Mat se1 = getStructuringElement(MORPH_RECT, Size(5, 5));
    Mat se2 = getStructuringElement(MORPH_RECT, Size(2, 2));

    // Perform closing then opening
    Mat mask;
    morphologyEx(img_bw, mask, MORPH_CLOSE, se1);
    morphologyEx(mask, mask, MORPH_OPEN, se2);

    // Filter the output
    Mat out = img.clone();
    out.setTo(Scalar(0), mask == 0);

    // Show image and save
    namedWindow("Output", WINDOW_NORMAL);
    imshow("Output", out);
    waitKey(0);
    destroyWindow("Output");
    imwrite("output.png", out);
}

結果は、Pythonバージョンで得られるものと同じになるはずです。

70
rayryeng

Skimageで remove_small_objects 関数を使用して、小さなピクセルクラスターを削除することもできます。

import matplotlib.pyplot as plt
from skimage import morphology
import numpy as np
import skimage

# read the image, grayscale it, binarize it, then remove small pixel clusters
im = plt.imread('spots.png')
grayscale = skimage.color.rgb2gray(im)
binarized = np.where(grayscale>0.1, 1, 0)
processed = morphology.remove_small_objects(binarized.astype(bool), min_size=2, connectivity=2).astype(int)

# black out pixels
mask_x, mask_y = np.where(processed == 0)
im[mask_x, mask_y, :3] = 0

# plot the result
plt.figure(figsize=(10,10))
plt.imshow(im)

これは表示します:

enter image description here

大きなクラスターのみを保持するには、min_size(保持するクラスターの最小サイズ)を増やし、connectivity(クラスターを形成するときのピクセル近傍のサイズ)を減らしてみてください。これらの2つのパラメーターのみを使用すると、適切なサイズのピクセルクラスターのみを保持できます。

0
duhaime