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

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

新規登録して質問してみよう
ただいま回答率
85.50%
C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Q&A

解決済

3回答

7301閲覧

ドーナツの画像の背景色を塗りつぶしたいです。

startnote

総合スコア24

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

0グッド

0クリップ

投稿2016/06/04 14:13

いつもお世話になります。
画像処理をしています。使っているのはC#ですが、ごく汎用的な話かもしれません。

ドーナツの画像の背景色を塗りつぶしたいです。
背景色は、R=255, G=255, B=255の白です。
これを、たとえば赤R=255, G=0, B=0にしたいです。

Bitmap bitmap = new Bitmap("ドーナツ.png");
Bitmap newbitmap = new Bitmap(bitmap.Width, bitmap.Height);
for (int height = 0; height < bitmap.Height; height++) {
for (int width = 0; width < bitmap.Width; width++) {
Color originalColor = bitmap.GetPixel(width, height);
//背景色の場合。
if (originalColor.R == 255 &&
originalColor.G == 255 &&
originalColor.B == 255)
newbitmap(
SetPixel(
width,
height,
Color.FromArgb(255,255,0,0)));
//それ以外の場合。
else
newbitmap(
SetPixel(
width,
height,
originalColor));
}
}
としてみましたが、これだと、もしドーナツのなかに白い砂糖がかかっていて、それがR=255, G=255, B=255の場合、そこも赤くなってしまいます。
砂糖をのぞいて、ドーナツの穴を含むドーナツの背景を塗りつぶすには、どのようにしたらよいでしょう?

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

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

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

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

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

guest

回答3

0

ペイントツールで元画像を開いて背景部分を透明色に変更して保存し、塗りつぶしたい色でべた塗りされた画像に描画するという手もあります。

投稿2016/06/05 21:12

Zuishin

総合スコア28656

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

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

startnote

2016/06/07 08:31

まあ、そういうのも当然ですがありますね~。 いちおうプログラムで処理したいのではありますが、まあ微妙ですね。おっしゃるとおり、大人な解決であります。はい。
Zuishin

2016/06/07 08:42

プログラムで処理する方法もあることはありますが敢えて書いていません。実用面で言えばユーザーの前で時間を取ることはなるべく避けるよう設計するのが定石ですから。
Zuishin

2016/06/07 10:00

参考までに書いておきます。もっと簡潔で効率とメンテナンス性に優れた方法がある以上、あくまで遊びですが。 まず画像の左上の色を取得します。 これを基準色と名付けます。 次に画像全体を左上から右に、上から下に捜査します。 たとえば y=10 の位置で x=3 から x=5 まで基準色が並んでいたとします。 さらにこれが今まで発見されたものとは隣接していないとします。 この時、新しいリストを作成し、10 をキーとしてこれにサブリストを追加し、サブリストに start=3, end=5 というプロパティを持つオブジェクトを追加します。 さらに親リストに「面積」というプロパティを追加し、これを 3 とします。 同様に捜査していき、座標と面積を更新します。 発見された基準色の位置が既存のリストに隣接していればそのリストに追加し、なければ新しいリストを作成します。 複数のリストに隣接していた場合は、リストをマージします。 隣接の判定は、各リストの末尾と比較すれば良いだけなので、さほど時間はかからないはずです。 こうして、リストが複数作られました。各リストは面積プロパティを持ち、基準色の座標を保持しています。 この中で、面積がある一定の域値より小さいものは削除します。これはつまり砂糖対策です。 こうしてできたリストの各要素の座標に従って色を塗れば、ドーナツの背景だけを塗りつぶすことができます。
startnote

2016/06/07 14:39

なるほど。閾値を設定するのですね。おもしろいです。
guest

0

こんにちは。

地道に白が連続している領域を抽出して塗りつぶすのが一般的と思います。
画像の外周に接している「白」を1点見つけて、そこを起点に上下左右ピクセル、もしくは、上下左右斜めピクセルをチェックして「白」なら連続していると判断して塗りつぶすことになります。
もし、背景が繋がっていない場合がある時は、全ての外周に接している点について同様な処理をします。

もし、画像処理ライブラリが使えるなら、恐らくラベリング処理を持っていると思いますので、それを利用すると楽できます。
無償の画像処理ライブラリの1つにOpenCVがありますが、C#から使えるラッパもあるようです。

投稿2016/06/04 15:18

Chironian

総合スコア23272

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

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

startnote

2016/06/07 08:27

ありがとうございます。 おっしゃるとおりでOpenCVを使う手もありますね。 使ったことはあります。 ただ独特で、今回の用途(exe単体で実行したい)にはやや不向きかと思いました。 ラベリング処理など、とても勉強になりました。
guest

0

ベストアンサー

Flood fillというやつですね。昔はペイントルーチンなどと呼んだりしていましたが、グラフィックスをVRAM直描きしていた頃はよく作ったものです。

Windows APIのExtFloodFill関数がまさにそれなのですが、なぜか.NET Frameworkには同様の機能がありません。C#からAPI呼び出ししてみましたがうまく使えませんでした。

ということで、アルゴリズム自体は単純なので昔を思い出しながら作ってみました。検索すればいろいろ見つかると思います。
クラスにしていますが、体裁や効率、エラー処理などはあまり考えていないので、適宜改良・改造してください。

C#

1class FloodFill 2{ 3 private Bitmap mBitmap; 4 private int mWidth; 5 private int mHeight; 6 private Color mFillColor; 7 private Color mSeedColor; 8 Queue<Point> mQueue = new Queue<Point>(); 9 10 public FloodFill(Bitmap iImage) 11 { 12 mBitmap = iImage; 13 mWidth = mBitmap.Width; 14 mHeight = mBitmap.Height; 15 } 16 17 public void Fill(int iStartX, int iStartY, Color iColor) 18 { 19 mFillColor = iColor; 20 mSeedColor = mBitmap.GetPixel(iStartX, iStartY); 21 mQueue.Enqueue(new Point(iStartX, iStartY)); 22 while(mQueue.Count != 0) 23 { 24 Point pos = mQueue.Dequeue(); 25 scanLine(pos); 26 } 27 } 28 29 private void scanLine(Point iPos) 30 { 31 if(!isFillArea(iPos.X, iPos.Y)) 32 return; 33 // 指定位置から右方向に塗って右端を覚えておく 34 int x = iPos.X; 35 while(x < mWidth && isFillArea(x, iPos.Y)) 36 { 37 mBitmap.SetPixel(x, iPos.Y, mFillColor); 38 x++; 39 } 40 int right = x - 1; 41 // 指定位置から左方向に塗って左端を覚えておく 42 x = iPos.X - 1; 43 while(x >= 0 && isFillArea(x, iPos.Y)) 44 { 45 mBitmap.SetPixel(x, iPos.Y, mFillColor); 46 x--; 47 } 48 int left = x + 1; 49 50 // 左端から右端の上下のラインをスキャンして塗りつぶし位置をキューに入れる 51 bool upperFound = false; 52 bool lowerFound = false; 53 for(x = left; x <= right; x++) 54 { 55 bool scan = isFillArea(x, iPos.Y - 1); 56 if(!upperFound) 57 { 58 if(scan) 59 { 60 mQueue.Enqueue(new Point(x, iPos.Y - 1)); 61 upperFound = true; 62 } 63 } 64 else 65 { 66 if(!scan) 67 { 68 upperFound = false; 69 } 70 } 71 scan = isFillArea(x, iPos.Y + 1); 72 if(!lowerFound) 73 { 74 if(scan) 75 { 76 mQueue.Enqueue(new Point(x, iPos.Y + 1)); 77 lowerFound = true; 78 } 79 } 80 else 81 { 82 if(!scan) 83 { 84 lowerFound = false; 85 } 86 } 87 } 88 } 89 90 private bool isFillArea(int iX, int iY) 91 { 92 if(iY < 0 || iY >= mHeight) 93 return false; 94 return mBitmap.GetPixel(iX, iY) == mSeedColor; 95 } 96}

Fillメソッドに塗りつぶしの開始座標と色を渡せば、その座標と同じ色の領域を指定した色で塗りつぶします。
分断された領域には届かないので、ドーナツの外側と内側の2回塗りつぶしを実行することになります。

投稿2016/06/05 01:04

catsforepaw

総合スコア5938

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

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

startnote

2016/06/07 08:29

ドーナツの中で実行する必要があるのですね。 いま漠然と考えているのは、塗り残しがあった場合には(というか判定できないのでいつも)、結果を出して、ユーザーにマウスでクリックしてもらう、ということしかないかな~、という感じです。
catsforepaw

2016/06/07 09:27

塗りつぶし自体は2回実行しないといけませんが、一定面積以上の白のべた塗り部分があればそれは背景、というように背景部分を探してそこを塗りつぶすようにすれば、ユーザーの介入なしでも背景を塗りつぶせると思います。
startnote

2016/06/07 14:40

一定面積、というのを使ってみようと思いました。ありがとうございます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.50%

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

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

質問する

関連した質問