ネタもと:http://www.atmarkit.co.jp/bbs/phpBB/viewtopic.php?topic=25346&forum=7
①ある画像に背景(緑)とは異なった色の点(赤)を4つ記し,その4点をVBによって認識し,四角形を作成したい。
②その四角形を領域とし,その領域内を2値化するプログラムを作成したい。
4点を抽出する方法を説明します。
まず、抽出しやすくします。1つの画像から4点を抽出しようとするから、難しいのです。「その点が、4点のうちどれにあたるか」などを考えないといけませんから。なので、1つの画像から1つの点を抽出することを考えます。
この事例では、4点は「画像の4角近くにある」ことが期待できます。ですから、画像を左上、右上、左下、右下の4つに分割します。
これで1つの画像から1点のみ、抽出すればいいことになります。そして、元の画像の配置を考えれば、点の位置関係を復元することが出来ます。

// originalImage に、元の画像があるとする
int holizontal = originalImage.Width / 2;
int vertical = originalImage.Height / 2;
Bitmap[] img = new Bitmap[4];
img[0] = new Bitmap(holizontal, vertical);
img[1] = new Bitmap(originalImage.Width - holizontal, vertical);
img[2] = new Bitmap(holizontal, originalImage.Height - vertical);
img[3] = new Bitmap(originalImage.Width - holizontal, originalImage.Height - vertical);
Graphics original = Graphics.FromImage(originalImage);
Graphics[] grp = new Graphics[4];
grp[0] = Graphics.FromImage(img[0]);
grp[1] = Graphics.FromImage(img[1]);
grp[2] = Graphics.FromImage(img[2]);
grp[3] = Graphics.FromImage(img[3]);
grp[0].DrawImage(originalImage
, new Rectangle(0, 0, img[0].Width, img[0].Height)
, new Rectangle(0, 0, holizontal, vertical)
, GraphicsUnit.Pixel);
grp[1].DrawImage(originalImage
, new Rectangle(0, 0, img[1].Width, img[1].Height)
, new Rectangle(holizontal, 0, originalImage.Width - holizontal, vertical)
, GraphicsUnit.Pixel);
grp[2].DrawImage(originalImage
, new Rectangle(0, 0, img[2].Width, img[2].Height)
, new Rectangle(0, vertical, holizontal, originalImage.Height - vertical)
, GraphicsUnit.Pixel);
grp[3].DrawImage(originalImage
, new Rectangle(0, 0, img[3].Width, img[3].Height)
, new Rectangle(holizontal, vertical, originalImage.Width - holizontal
, originalImage.Height - vertical)
, GraphicsUnit.Pixel);
抽出するべき点(探索始点)を探す方法を説明します。
まぁ、左上から順に探せばいいのですが、画像の中心付近にあるだろうという仮定の下、中心から探すことにします。
ここでは、HSV で分析して、点を探すことにします。Hue の範囲を0~15と、290~360にしました。「赤みがかった」と思える範囲です。また、Britness が 0.5 を下回ると「黒」とほとんど見分けが付かないので棄てます。また、0.9 以上、明るすぎるところも棄てています。彩度も、0.2 以下は棄てます。この辺は「調整の必要がある」ということで。

private bool IsSearchingColor(Color c) {
if (c.GetHue() < 15.0 || c.GetHue() > 290.0) {
if (c.GetBrightness() < 0.9 && c.GetBrightness() > 0.5
&& c.GetSaturation() > 0.2) {
return true;
}
}
return false;
}
private Point SearchPoint(Bitmap image) {
int w = (int)Math.Ceiling(image.Width / 2.0);
int h = (int)Math.Ceiling(image.Height / 2.0);
for (int idxH = 0; idxH < h; idxH++) {
int y01 = h - idxH;
int y23 = h + idxH > image.Height ? image.Height : h + idxH;
for (int idxW = 0; idxW < w; idxW++) {
int x02 = w - idxW;
int x13 = w + idxW > image.Width ? image.Width : w + idxW;
Point[] p = new Point[4];
p[0] = new Point(x02, y01);
p[1] = new Point(x13, y01);
p[2] = new Point(x02, y23);
p[3] = new Point(x13, y23);
for (int idxP = 0; idxP < 4; idxP++) {
Color c = image.GetPixel(p[idxP].X, p[idxP].Y);
if (IsSearchingColor(c)) { return p[idxP]; }
}
}
}
return new Point(-1, -1);
}
抽出するべき点を拡大する方法を説明します。
先に出した探索始点(*)の周り8点が、抽出すべき色かどうかを調べ、登録します。inArea には「抽出するべき色だった点」、points には、「次に調べる中心となる点」が入ります。次の図で、3を中心に調査するとき、4が重複しますが、HashTable.Contains により、登録を省きます。

private Rectangle Extent(Bitmap image, Point startPoint) {
System.Collections.Hashtable inArea = SearchPointsInArea(image, startPoint);
System.Collections.IEnumerator ipt = inArea.GetEnumerator();
Point topLeft = new Point(startPoint.X, startPoint.Y);
Point bottomRight = new Point(startPoint.X, startPoint.Y);
while (ipt.MoveNext()) {
Point pt = (Point)inArea[((System.Collections.DictionaryEntry)ipt.Current).Key];
if (topLeft.X > pt.X) { topLeft.X = pt.X; }
if (topLeft.Y > pt.Y) { topLeft.Y = pt.Y; }
if (bottomRight.X < pt.X) { bottomRight.X = pt.X; }
if (bottomRight.Y < pt.Y) { bottomRight.Y = pt.Y; }
}
return new Rectangle(topLeft.X, topLeft.Y
, bottomRight.X - topLeft.X, bottomRight.Y - topLeft.Y);
}
public System.Collections.Hashtable SearchPointsInArea(Bitmap image, Point startPoint) {
System.Collections.Hashtable inArea = new System.Collections.Hashtable();
System.Collections.Queue points = new System.Collections.Queue();
points.Enqueue(startPoint);
while (points.Count > 0) {
Point center = (Point)points.Dequeue();
for (int idx = 0; idx < 8; idx++) {
Point inspect = RoundPoint(center, idx);
Color c = image.GetPixel(inspect.X, inspect.Y);
if (IsSearchingColor(c)) {
if (!inArea.Contains(inspect)) {
inArea.Add(inspect, inspect);
points.Enqueue(inspect);
}
}
}
}
return inArea;
}
private Point RoundPoint(Point center, int index) {
switch (index) {
case 0:
return new Point(center.X, center.Y + 1);
case 1:
return new Point(center.X - 1, center.Y + 1);
case 2:
return new Point(center.X - 1, center.Y);
case 3:
return new Point(center.X - 1, center.Y - 1);
case 4:
return new Point(center.X, center.Y - 1);
case 5:
return new Point(center.X + 1, center.Y - 1);
case 6:
return new Point(center.X + 1, center.Y);
case 7:
return new Point(center.X + 1, center.Y + 1);
case 8:
return new Point(center.X, center.Y);
}
return center;
}
これで4点の抽出が出来ました。結果は次のようになりました。右上に当たるところが、ゴミを拾ってしまっています。また、探索始点の探索を、横方向を優先して行っていますが、縦方向を優先すると、左上以外はゴミを拾ってしまいます。

領域にするには、マーカーのある領域が出てきましたから、どういう範囲を領域にするかを決めます。つまり、各マーカーの中心を結ぶとか、マーカーを含まない内側とか。決めた点を Polygon クラス(を作って) に放り込めば、領域ができあがります。
投稿日時 : 2005年10月25日 19:12