質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
OpenCV

OpenCV(オープンソースコンピュータービジョン)は、1999年にインテルが開発・公開したオープンソースのコンピュータビジョン向けのクロスプラットフォームライブラリです。

Android

Androidは、Google社が開発したスマートフォンやタブレットなど携帯端末向けのプラットフォームです。 カーネル・ミドルウェア・ユーザーインターフェイス・ウェブブラウザ・電話帳などのアプリケーションやソフトウェアをひとつにまとめて構成。 カーネル・ライブラリ・ランタイムはほとんどがC言語/C++、アプリケーションなどはJavaSEのサブセットとAndroid環境で書かれています。

Q&A

解決済

1回答

4932閲覧

floodFillでの塗りつぶしの色を透明にする方法について

kenta001

総合スコア16

OpenCV

OpenCV(オープンソースコンピュータービジョン)は、1999年にインテルが開発・公開したオープンソースのコンピュータビジョン向けのクロスプラットフォームライブラリです。

Android

Androidは、Google社が開発したスマートフォンやタブレットなど携帯端末向けのプラットフォームです。 カーネル・ミドルウェア・ユーザーインターフェイス・ウェブブラウザ・電話帳などのアプリケーションやソフトウェアをひとつにまとめて構成。 カーネル・ライブラリ・ランタイムはほとんどがC言語/C++、アプリケーションなどはJavaSEのサブセットとAndroid環境で書かれています。

0グッド

0クリップ

投稿2018/10/06 05:16

編集2018/10/07 03:50

前提・実現したいこと

floodFillを活用して、色の塗りつぶしを作成しています。
塗りつぶしの色を透明にする方法が全く分からない状態です。
※透明でなければあっさりとできました。

そもそも可能なのでしょうか?

また、サンプルコードなど探してみたのですが、
見つかりませんでした。
どこかに似たサンプルコードはありますでしょうか?

全く方法がわからず、大雑把な質問となってしまい申し訳ございません。

よろしくお願いします。

Android(java)

1private void drawing() { 2 3//■Matの作成 4Mat mat_original = new Mat(); 5Mat mat = new Mat(); 6 7//■Bitmapをmatに変換 8Utils.bitmapToMat(bitmap_toumei, mat_original); 9Utils.bitmapToMat(bitmap_toumei, mat); 10 11//■チャネルの変更(floodfillは透明を扱えないため) 12Imgproc.cvtColor(mat, mat, Imgproc.COLOR_RGBA2GRAY); //変換種類 https://docs.opencv.org/java/2.4.9/org/opencv/imgproc/Imgproc.html 13 14org.opencv.core.Point seedPoint = new org.opencv.core.Point(seedCoordinateList.get(seedCoordinateList.size()-1).cX, seedCoordinateList.get(seedCoordinateList.size()-1).cY); 15 16//■輝度値/色の差の許容下限値の設定 17double diff = 50.0; 18 19//■Maskデータ作成 cv::Mat mask = cv::Mat::zeros(image.rows + 2, image.cols + 2, CV_8UC1); 20Mat mat2 = new Mat(mat.height() + 2 , mat.width() + 2, CV_8UC1); 21 22//■色変換 23Imgproc.floodFill( 24mat, 25mat2, 26seedPoint, 27new Scalar(.0, .0, .0, .0), 28new org.opencv.core.Rect(), 29new Scalar(diff), 30new Scalar(diff), 314 | Imgproc.FLOODFILL_MASK_ONLY | (255 << 8) 32//Imgproc.FLOODFILL_FIXED_RANGE | 4 //種類: FLOODFILL_FIXED_RANGE:http://opencv.jp/opencv-2.1/cpp/miscellaneous_image_transformations.html 33 34); 35 36//■mask == 255 のピクセルの透過度を 255 にする。 37org.opencv.core.Rect roi = new org.opencv.core.Rect(1, 1, mat.width(), mat.height()); 38mat2 = mat2.submat(roi); 39mat_original = mat_original.setTo(new Scalar(.0, .0, .0, .0),mat2); 40 41//■■bitmapに変換 42Bitmap output = Bitmap.createBitmap(bitmap_toumei.getWidth(), bitmap_toumei.getHeight(), Bitmap.Config.ARGB_8888); 43Utils.matToBitmap(mat_original ,output,true); 44bitmap_toumei = output; 45 46//■再描画 47invalidate(); 48 49}

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

cv::floodFill() の入力は1チャンネルまたは3チャンネルしか扱えないので、直接連結成分を透過にするというのはできません。
しかし、cv::floodFill() には、処理した画素を mask 引数経由で受け取ることができるので、これを利用して元画像のうち、処理した画素だけアルファチャンネルを 0 に設定することで質問の内容を達成できます。

floodFill() の引数の意味は、リファレンス cv::floodFill() を参考にしてください。
わからない点があれば、補足します。

サンプルコード

イメージ説明
入力画像

このうち、ピクセル (10, 10) と色で繋がっている部分を透過することを目指します。

コード

cpp

1#include <opencv2/opencv.hpp> 2 3int main() 4{ 5 cv::Mat image = cv::imread(R"(test.png)"); 6 7 // マスク画像作成 8 cv::Mat mask = cv::Mat::zeros(image.rows + 2, image.cols + 2, CV_8UC1); 9 cv::floodFill( 10 image, 11 mask, 12 cv::Point(10, 10), 13 cv::Scalar(0), 14 nullptr, 15 cv::Scalar(), 16 cv::Scalar(), 17 4 | cv::FLOODFILL_MASK_ONLY | (255 << 8)); 18 // 端の1ピクセルを除く (height + 2, width + 2) -> (height, width) 19 mask = cv::Mat(mask, cv::Rect(1, 1, image.rows, image.cols)); 20 cv::imwrite("mask.png", mask); 21 22 // mask == 255 のピクセルの透過度を 255 にする。 23 cv::cvtColor(image, image, cv::COLOR_BGR2BGRA); 24 // mask == 255 のピクセルの透過度を 255 にする。 25 image = image.setTo(cv::Scalar(0), mask); 26 cv::imwrite("rgba.png", image); 27}

イメージ説明
マスク画像

イメージ説明
結果画像

追記

グレースケール画像に変換している部分について

//■チャネルの変更(floodfillは透明を扱えないため) Imgproc.cvtColor(mat, mat, Imgproc.COLOR_RGBA2GRAY)

グレースケールに変換すると RGB の情報量は落ちてしまうので、同じ色を塗りつぶしたいのであれば、グレースケールにする必要はないと思います。

イメージ説明
カラー画像

イメージ説明
グレースケールに変換した画像

mask を初期化していない。

Mat mat2 = new Mat(mat.height() + 2 , mat.width() + 2, CV_8UC1);

floodFill() のリファレンスを見ると、次のように書いてあります。

Since this is both an input and output parameter, you must take responsibility of initializing it.

floodFill() は、指定した点 seed の連結成分を走査する際、mask が0の画素だけ探します。mask が非0の画素は色が同じでも走査しません。名前通り、マスクとして機能するわけです。
また、floodFill() で見つけた連結成分には、mask に値が書き込まれます。この値は flag 引数の 8 ~ 16 ビット目で指定します。(255 << 8 としている部分です。この場合、連結成分に 255 が書き込まれます。)

このように入力または出力の両方で利用される引数であるため、mask の値を初期化するのは呼び出し側の責任になります。

今回は、mask はマスクとしては利用せず、走査した結果を受け取るためだけに利用します。走査はすべての画素を対象に行ってほしいので、mask の全要素は 0 で初期化します。

そのため、次のようにしていたのですが

//■Maskデータ作成 cv::Mat mask = cv::Mat::zeros(image.rows + 2, image.cols + 2, CV_8UC1);

このようにしてしまうと、mat2 の各値は未定義になってしまうので、floodFill() の結果もおかしくなると思われます。

Mat mat2 = new Mat(mat.height() + 2 , mat.width() + 2, CV_8UC1);

C++ 版には全部0で初期化した行列の作成には、cv::Mat::zeros() の他に、以下のものがありますが、Java ではどうでしょうか。

Mat(int rows, int cols, int type, const Scalar &s) Mat(Size size, int type, const Scalar &s)

チャンネルの並び順

チャンネルの並び順には RGB と BGR があり、imread() で読み込んだ場合は、並び順は BGR になります。
他のライブラリで読み込んだ画像の場合は RGB が多いです。
そのため、COLOR_RGBA2GRAYCOLOR_BGRA2GRAY では意味がかわってきますので、注意が必要です。

Imgproc.cvtColor(mat, mat, Imgproc.COLOR_RGBA2GRAY);

チャンネルの並び順を確認するには、cv::imwrite() で書き出すとわかります。(チャンネルが反転してたらRGB、反転してなかったらBGR)

他に気になる点

BGR から BGRA に変換する処理が見当たりませんが、
mat_original は以下の関数で変換すると、すでに BGRA になっているのでしょうか?

Utils.bitmapToMat(bitmap_toumei, mat_original);

連結成分と扱う基準について

完全に一致した画素でなく、ある程度似た色は同じ連結成分にしたいという幅を持たせるために loDiff, upDiff という引数があります。

同じ連結成分と判定する基準は floodFill() のリファレンスに記載があります。

flag に cv::FLOODFILL_FIXED_RANGE を指定した場合 隣接する画素値 - loDiff <= 走査中の画素 <= 隣接する画素値 + upDiff flag に cv::FLOODFILL_FIXED_RANGE を指定していない場合 隣接する画素値 - loDiff <= seed の画素値 <= 隣接する画素値 + upDiff

loDiff, upDiff はグレースケールの場合は、cv::Scalar(s1), カラー画像の場合は cv::Scalar(s1, s2, s3) と指定します。

cv::FLOODFILL_FIXED_RANGE をつけるかどうかの違いは、似ているかどうか判断する際の比較する画素が seed の点なのか、隣接する点なのかの違いです。
以下のグラデーションの画像に各引数で試した結果です。

イメージ説明
オリジナル画像

イメージ説明
cv::FLOODFILL_FIXED_RANGE, loDiff=cv::Scalr(0, 0, 0), upDiff=cv::Scalr(0, 0, 0)

イメージ説明
loDiff=cv::Scalr(0, 0, 0), upDiff=cv::Scalr(0, 0, 0)

イメージ説明
cv::FLOODFILL_FIXED_RANGE, loDiff=cv::Scalr(10, 10, 10), upDiff=cv::Scalr(10, 10, 10)

イメージ説明
loDiff=cv::Scalr(10, 10, 10), upDiff=cv::Scalr(10, 10, 10)

このパラメータをうまく調整することで精度を上げることができるかもしれません。
また似た色を同じ色として塗りつぶしたい場合は、第1引数 image の入力を RGB ではなく、cvtColor() で HSV に変換したほうがいいかもしれません。
OpenCVでのHSV色空間lower,upperの取り扱い

投稿2018/10/06 07:40

編集2018/10/07 04:21
tiitoi

総合スコア21956

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

kenta001

2018/10/06 09:57

ご回答ありがとうございます。 サンプルまで用意していただき、非常に助かりました。 こちらの情報で進めてみます。
kenta001

2018/10/07 01:57

お世話になってります。 上記の内容で達成することができました。 ありがとうございます。 ですが、1点問題がでてしまいました。 なぜか全部塗りつぶされてしまうタイミングあり、調査したところ maskのデータが全然ことなるデータが生成されるときがあります。 (かなり拡大された状態のようななのか?全体にうすい黒い状態が広がってるような感じです) こちらの原因は予想つきますでしょうか? ※綺麗に生成されるときもあります。 大変申し訳ございませんが、ご教授お願いしても宜しいでしょうか?
tiitoi

2018/10/07 02:06

画像と試したコードを貼ることはできますか?現象が再現する画像及びコードがないと検証は難しいです。
kenta001

2018/10/07 02:45 編集

Android(java)で作成しております。 ・floodfillした際に、あらくなってしまうためCOLOR_RGBA2GRAYにしているのですが 、こちら原因の可能性はありますでしょうか? COLOR_RGBA2GRAYしないと、あらくなってしまうのも、おかしいとは思っているのですが。。 ・画像貼り付けますので、お待ちください。 private void drawing() { //■Matの作成 Mat mat_original = new Mat(); Mat mat = new Mat(); //■Bitmapをmatに変換 Utils.bitmapToMat(bitmap_toumei, mat_original); Utils.bitmapToMat(bitmap_toumei, mat); //■チャネルの変更(floodfillは透明を扱えないため) Imgproc.cvtColor(mat, mat, Imgproc.COLOR_RGBA2GRAY); //変換種類 https://docs.opencv.org/java/2.4.9/org/opencv/imgproc/Imgproc.html org.opencv.core.Point seedPoint = new org.opencv.core.Point(seedCoordinateList.get(seedCoordinateList.size()-1).cX, seedCoordinateList.get(seedCoordinateList.size()-1).cY); //■輝度値/色の差の許容下限値の設定 double diff = 50.0; //■Maskデータ作成 cv::Mat mask = cv::Mat::zeros(image.rows + 2, image.cols + 2, CV_8UC1); Mat mat2 = new Mat(mat.height() + 2 , mat.width() + 2, CV_8UC1); //■色変換 Imgproc.floodFill( mat, mat2, seedPoint, new Scalar(.0, .0, .0, .0), new org.opencv.core.Rect(), new Scalar(diff), new Scalar(diff), 4 | Imgproc.FLOODFILL_MASK_ONLY | (255 << 8) //Imgproc.FLOODFILL_FIXED_RANGE | 4 //種類: FLOODFILL_FIXED_RANGE:http://opencv.jp/opencv-2.1/cpp/miscellaneous_image_transformations.html ); //■mask == 255 のピクセルの透過度を 255 にする。 org.opencv.core.Rect roi = new org.opencv.core.Rect(1, 1, mat.width(), mat.height()); mat2 = mat2.submat(roi); mat_original = mat_original.setTo(new Scalar(.0, .0, .0, .0),mat2); //■■bitmapに変換 Bitmap output = Bitmap.createBitmap(bitmap_toumei.getWidth(), bitmap_toumei.getHeight(), Bitmap.Config.ARGB_8888); Utils.matToBitmap(mat_original ,output,true); bitmap_toumei = output; //■再描画 invalidate(); }
tiitoi

2018/10/07 03:17

質問欄は何度も編集できるので、コードは質問欄に ```コード``` と markdown のコードブロックで記載されるといいかと思います。おそらく原因として考えられるのは、mask の値を0で初期化していないからです。回答を追記しましたので、ご確認ください。
kenta001

2018/10/07 03:45

申し訳ございません。 編集で変更いたします。 ご指摘ありがとうございます。 mask の値を0で初期化で直りました。 助かりました。 本当に感謝です!!。 >> 同じ色を塗りつぶしたいのであれば、グレースケールにする必要はないと思います。 こちらですが、赤→青のように単純に変換するのではなく、 隣接の色が少し違う程度であれば塗りつぶしたいと思っています。 ※輝度値/色の差の許容下限値の設定【こちら】 グレースケールにしなかった場合、色の差の許容下限値の設定を100に設定しても、 なめらかに変換されません。 これは、仕方ないという認識になりますでしょうか? ※何度も質問ばかり申し訳ございません。
tiitoi

2018/10/07 04:20

以下で最適なやり方を見つけることで改善されると思います。 回答も追記しました。 (1) loDiff、upDiff の値を調整する。 グレースケールの場合は cv::Scalar(s1) カラーの場合は cv::Scalar(s1, s2, s3) (2) cv::FLOODFILL_FIXED_RANGE をつけるか、つけないか (3) 入力画像を RGB でなく、HSV 色空間に変換する。
kenta001

2018/10/07 04:37

ご回答ありがとうございます! 完璧にイメージどおりのものができました。 詳しいご解説までありがとうございます!!!。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問