查找图像内容并在其周围绘制矩形

关于

我正在使用WinForms 。 在我的表格中,我有一个图片picturebox 。 图片picturebox大小模式设置为zoom 。 我使用图片picturebox查看TIF图像。 TIF图像为灰度( 仅限黑白 )。

我的应用程序做什么

我的应用程序在文档中找到第一个黑色像素和最后一个黑色像素,并在其周围绘制一个红色矩形。 希望它能在图像内容周围绘制矩形。

问题

有时,TIF文档在图像内容周围有斑点/点。 这会抛弃我的应用程序。 它不知道图像内容的开始和结束位置。 如果文档有斑点/点,我怎样才能找到TIF文档的内容并在其周围画一个矩形?

关于文件

  • 仅黑白(1位深度)
  • TIF文件
  • 文档总是有字母和数字
  • 字母和数字总是大于斑点/点
  • 即使在内容中,图像上也可能存在多个点
  • 背景总是白色的
  • 内容总是黑色的
  • 斑点也是黑色的

下载测试图像链接:

•File Dropper: http //www.filedropper.com/test-tifs

•快速共享: https //ufile.io/2qiir


我找到了什么

在我的研究中,我找到了AForge.Imaging库,它有许多可能有助于我实现目标的成像filter。 我正在考虑使用中值滤波器去除斑点/点,或者使用其他滤波器来获得所需的结果。

我试过的

我尝试应用AForge库中的中值滤波器来摆脱斑点,但只是摆脱了一些斑点。 我不得不重复多次回复filter以摆脱大部分的点找到内容,但仍然很难找到内容。 那种方法对我来说效果不好。

链接到AForgefilter: http ://www.aforgenet.com/framework/docs/html/cdf93487-0659-e371-fed9-3b216efb6954.htm


  private void btn_Draw_Click(object sender, EventArgs e) { // Wrap the creation of the OpenFileDialog instance in a using statement, // rather than manually calling the Dispose method to ensure proper disposal using (OpenFileDialog dlg = new OpenFileDialog()) { if (dlg.ShowDialog() == DialogResult.OK) { pictureBox1.Image = new Bitmap(dlg.FileName); int xMax = pictureBox1.Image.Width; int yMax = pictureBox1.Image.Height; startX = Int32.MaxValue; startY = Int32.MaxValue; endX = Int32.MinValue; endY = Int32.MinValue; using (Bitmap bmp = new Bitmap(pictureBox1.Image)) { for (var y = 0; y < yMax; y+=3) { for (var x = 0; x < xMax; x+=3) { Color col = bmp.GetPixel(x, y); if(col.ToArgb() == Color.Black.ToArgb()) { // Finds first black pixel if (x < startX) startX = x; if(y  endX) endX = x; if (y > endY) endY = y; } } } int picWidth = pictureBox1.Size.Width; int picHeight = pictureBox1.Size.Height; float imageRatio = xMax / (float)yMax; // image W:H ratio float containerRatio = picWidth / (float)picHeight; // container W:H ratio if (imageRatio >= containerRatio) { // horizontal image float scaleFactor = picWidth / (float)xMax; float scaledHeight = yMax * scaleFactor; // calculate gap between top of container and top of image float filler = Math.Abs(picHeight - scaledHeight) / 2; //float filler = 0; startX = (int)(startX * scaleFactor); endX = (int)(endX * scaleFactor); startY = (int)((startY) * scaleFactor + filler); endY = (int)((endY) * scaleFactor + filler); } else { // vertical image float scaleFactor = picHeight / (float)yMax; float scaledWidth = xMax * scaleFactor; float filler = Math.Abs(picWidth - scaledWidth) / 2; startX = (int)((startX) * scaleFactor + filler); endX = (int)((endX) * scaleFactor + filler); startY = (int)(startY * scaleFactor); endY = (int)(endY * scaleFactor); } //var scaleX = picWidth / (float)xMax; //var scaleY = picHeight / (float)yMax; //startX = (int)Math.Round(startX * scaleX); //startY = (int)Math.Round(startY * scaleY); //endX = (int)Math.Round(endX * scaleX); //endY = (int)Math.Round(endY * scaleY); } } } } private bool _once = true; private void pictureBox1_Paint(object sender, PaintEventArgs e) { if (_once) { //Rectangle ee = new Rectangle(35, 183, 405, 157); Rectangle ee = new Rectangle(startX, startY, endX - startX, endY - startY); System.Diagnostics.Debug.WriteLine(startX + ", " + startY + ", " + (endX - startX) + ", " + (endY - startY)); using (Pen pen = new Pen(Color.Red, 2)) { e.Graphics.DrawRectangle(pen, ee); } //_once = false; } } 

在内容周围没有任何斑点和点的Tif文档

在此处输入图像描述

内容周围有点和点的Tif文件 在此处输入图像描述

示例图1:


在此处输入图像描述

示例图2


在此处输入图像描述

示例图3


在此处输入图像描述

以下实验似乎符合您的所有要求。

我将以下控件放在Form1上

一个MenuStrip:Docking = Top,带有2个MenuItems – 一个用于打开文件,第二个用于运行算法

进度条:Docking = Top,用于观察加载和算法的性能

具有Docking = Fill和AutoScroll = true的面板

一张图片进入面板,Point(0,0),其余部分默认为。 SizeMode =正常。

更新

 using System; using System.Collections.Generic; using System.Drawing; using System.Windows.Forms; namespace WindowsFormsApplication1 { public partial class Form1 : Form { public Form1() { InitializeComponent(); } // Opens an image file. private void openToolStripMenuItem_Click(object sender, EventArgs e) { OpenFileDialog dlg = new OpenFileDialog(); if (dlg.ShowDialog() == System.Windows.Forms.DialogResult.OK) { this.image = Image.FromFile(dlg.FileName) as Bitmap; this.pictureBox1.Image = image; this.pictureBox1.Invalidate(); } } Bitmap image; // finds top, left, right and bottom bounds of the content in TIFF file. // private void findBoundsToolStripMenuItem_Click(object sender, EventArgs e) { int contentSize = 70; this.left = 0; this.top = 0; this.right = this.pictureBox1.Width - 1; this.bottom = this.pictureBox1.Height - 1; int h = image.Height; int w = image.Width; this.progressBar1.Value = 0; this.progressBar1.Maximum = 4; for (int y = 0; y < h; y++) { for (int x = 0; x < w; x++) { if (this.image.GetPixel(x, y).ToArgb() == Black) { int size = this.image.GetBlackRegionSize(x, y); if (this.image.GetBlackRegionSize(x, y) > contentSize) { this.top = y; goto label10; } } } } label10: this.progressBar1.Increment(1); for (int y = h - 1; y >= 0; y--) { for (int x = 0; x < w; x++) { if (this.image.GetPixel(x, y).ToArgb() == Black) { if (this.image.GetBlackRegionSize(x, y) > contentSize) { this.bottom = y; goto label11; } } } } label11: this.progressBar1.Increment(1); for (int x = 0; x < w; x++) { for (int y = 0; y < h; y++) { if (this.image.GetPixel(x, y).ToArgb() == Black) { if (this.image.GetBlackRegionSize(x, y) > contentSize) { this.left = x; goto label12; } } } } label12: this.progressBar1.Increment(1); for (int x = w - 1; x >= 0; x--) { for (int y = 0; y < h; y++) { if (this.image.GetPixel(x, y).ToArgb() == Black) { if (this.image.GetBlackRegionSize(x, y) > contentSize) { this.right = x; goto label13; } } } } label13: this.progressBar1.Increment(1); this.pictureBox1.Invalidate(); } internal static readonly int Black = Color.Black.ToArgb(); internal static readonly int White = Color.White.ToArgb(); int top; int bottom; int left; int right; private void pictureBox1_Paint(object sender, PaintEventArgs e) { if (pictureBox1.Image == null) { return; } int xMax = pictureBox1.Image.Width; int yMax = pictureBox1.Image.Height; int startX = this.left; int startY = this.top; int endX = this.right; int endY = this.bottom; int picWidth = pictureBox1.Size.Width; int picHeight = pictureBox1.Size.Height; float imageRatio = xMax / (float)yMax; // image W:H ratio float containerRatio = picWidth / (float)picHeight; // container W:H ratio if (imageRatio >= containerRatio) { // horizontal image float scaleFactor = picWidth / (float)xMax; float scaledHeight = yMax * scaleFactor; // calculate gap between top of container and top of image float filler = Math.Abs(picHeight - scaledHeight) / 2; //float filler = 0; startX = (int)(startX * scaleFactor); endX = (int)(endX * scaleFactor); startY = (int)((startY) * scaleFactor + filler); endY = (int)((endY) * scaleFactor + filler); } else { // vertical image float scaleFactor = picHeight / (float)yMax; float scaledWidth = xMax * scaleFactor; float filler = Math.Abs(picWidth - scaledWidth) / 2; startX = (int)((startX) * scaleFactor + filler); endX = (int)((endX) * scaleFactor + filler); startY = (int)(startY * scaleFactor); endY = (int)(endY * scaleFactor); } //if (_once) //Rectangle ee = new Rectangle(35, 183, 405, 157); Rectangle ee = new Rectangle(startX, startY, endX - startX, endY - startY); System.Diagnostics.Debug.WriteLine(startX + ", " + startY + ", " + (endX - startX) + ", " + (endY - startY)); using (Pen pen = new Pen(Color.Red, 2)) { e.Graphics.DrawRectangle(pen, ee); } //_once = false; } } static class BitmapHelper { internal static int GetBlackRegionSize(this Bitmap image, int x, int y) { int size = 0; GetRegionSize(image, new List(), x, y, 0, ref size); return size; } // this constant prevents StackOverFlow exception. // also it has effect on performance. // It's value must be greater than the value of contentSize defined in findBoundsToolStripMenuItem_Click(object sender, EventArgs e) method. const int MAXLEVEL = 100; static void GetRegionSize(this Bitmap image, List list, int x, int y, int level, ref int size) { if (x >= image.Width || x < 0 || y >= image.Height || y < 0 || list.Contains(x, y) || image.GetPixel(x, y).ToArgb() != Form1.Black || level > MAXLEVEL) { return; } if (size < level) { size = level; } list.Add(new Point(x, y)); image.GetRegionSize(list, x, y - 1, level + 1, ref size); image.GetRegionSize(list, x, y + 1, level + 1, ref size); image.GetRegionSize(list, x - 1, y, level + 1, ref size); image.GetRegionSize(list, x + 1, y, level + 1, ref size); } static bool Contains(this List list, int x, int y) { foreach (Point point in list) { if (point.X == x && point.Y == y) { return true; } } return false; } } } 

“this.pictureBox1.Size = image.Size;” 已被删除。 Paint事件处理程序的代码已更改。 PictureBox大小模式现在可以设置为Zoom。

截图

更新2

我试图简化代码并提高性能。

 using System; using System.Collections.Generic; using System.Drawing; using System.Windows.Forms; namespace WindowsFormsApplication3 { public partial class Form1 : Form { public Form1() { InitializeComponent(); this.pictureBox1.Paint += new PaintEventHandler(this.pictureBox1_Paint); } // Opens an image file // and runs "FindBounds()" method private void openToolStripMenuItem_Click(object sender, EventArgs e) { OpenFileDialog dlg = new OpenFileDialog(); if (dlg.ShowDialog() == System.Windows.Forms.DialogResult.OK) { this.image = Image.FromFile(dlg.FileName) as Bitmap; FindBounds(); this.pictureBox1.Image = image; this.pictureBox1.Invalidate(); } } Bitmap image; // Possible maximum side of a spot or a dot in the image int maxSpotOrDotSide = 7; // Finds top, left, right and bottom bounds of the content in TIFF file. private void FindBounds() { // Possible maximum area of a spot or a dot in the image int maxSpotOrDotArea = maxSpotOrDotSide * maxSpotOrDotSide; this.left = 0; this.top = 0; this.right = this.pictureBox1.Width - 1; this.bottom = this.pictureBox1.Height - 1; int h = image.Height; int w = image.Width; int num = w * h; // Incrementers. I tested with greater values // like "x = 2", "x = 5" and it increased performance. // But we must be carefull as this may cause skipping content. int dx = 1; // Incrementer for "x" int dy = 1; // Incrementer for "y" // Initialization of "progressBar1" this.progressBar1.Value = 0; this.progressBar1.Maximum = num; // Content of the image BlackContent imageContent = null; // Here we will scan pixels of the image // starting from top left corner and // finishing at bottom right for (int y = 0; y < h; y += dx) { for (int x = 0; x < w; x += dy) { int val = y * w + x; this.progressBar1.Value = val > num ? num : val; // This block skips scanning imageContent // thus should increase performance. if (imageContent != null && imageContent.Contains(x, y)) { x = imageContent.Right; continue; } // Interesting things begin to happen // after we detect the first Black pixel if (this.image.GetPixel(x, y).ToArgb() == Black) { BlackContent content = new BlackContent(x, y); // Start Flood-Fill algorithm content.FloodFill(this.image); if (content.Area > maxSpotOrDotArea) { if (imageContent == null) { imageContent = content; } imageContent.Include(content.Right, content.Bottom); imageContent.Include(content.Left, content.Top); } else { // Here it's better we increase values of the incrementers. // Depending on size of spots/dots. // It should increase performance. if (dx < content.Width) dx = content.Width; if (dy < content.Height) dy = content.Height; } } } } // Everything is done. this.progressBar1.Value = this.progressBar1.Maximum; // If image content has been detected // then we save the information if (imageContent != null) { this.left = imageContent.Left; this.top = imageContent.Top; this.right = imageContent.Right; this.bottom = imageContent.Bottom; } this.pictureBox1.Invalidate(); } internal static readonly int Black = Color.Black.ToArgb(); internal static readonly int White = Color.White.ToArgb(); int top; int bottom; int left; int right; private void pictureBox1_Paint(object sender, PaintEventArgs e) { if (pictureBox1.Image == null) { return; } int xMax = pictureBox1.Image.Width; int yMax = pictureBox1.Image.Height; int startX = this.left; int startY = this.top; int endX = this.right; int endY = this.bottom; int picWidth = pictureBox1.Size.Width; int picHeight = pictureBox1.Size.Height; float imageRatio = xMax / (float)yMax; // image W:H ratio float containerRatio = picWidth / (float)picHeight; // container W:H ratio if (imageRatio >= containerRatio) { // horizontal image float scaleFactor = picWidth / (float)xMax; float scaledHeight = yMax * scaleFactor; // calculate gap between top of container and top of image float filler = Math.Abs(picHeight - scaledHeight) / 2; //float filler = 0; startX = (int)(startX * scaleFactor); endX = (int)(endX * scaleFactor); startY = (int)((startY) * scaleFactor + filler); endY = (int)((endY) * scaleFactor + filler); } else { // vertical image float scaleFactor = picHeight / (float)yMax; float scaledWidth = xMax * scaleFactor; float filler = Math.Abs(picWidth - scaledWidth) / 2; startX = (int)((startX) * scaleFactor + filler); endX = (int)((endX) * scaleFactor + filler); startY = (int)(startY * scaleFactor); endY = (int)(endY * scaleFactor); } //if (_once) //Rectangle ee = new Rectangle(35, 183, 405, 157); Rectangle ee = new Rectangle(startX, startY, endX - startX, endY - startY); System.Diagnostics.Debug.WriteLine(startX + ", " + startY + ", " + (endX - startX) + ", " + (endY - startY)); using (Pen pen = new Pen(Color.Red, 2)) { e.Graphics.DrawRectangle(pen, ee); } //_once = false; } } // This class is similar to System.Drawing.Region class // except that its only rectangular. // Because all we need is to draw a rectagnle // around the image this property must // make it faster, at least I hope so. class BlackContent { internal void FloodFill(Bitmap image) { FloodFillPrivate(image, this.left + 1, this.top, 0); } // Legendary Flood-Fill algorithm. // Quite often it ends up with StackOverFlow exception. // But this class and its rectangularity property // must prevent this disaster. // In my experiments I didn't encounter incidents. void FloodFillPrivate(Bitmap image, int x, int y, int level) { if (x >= image.Width || x < 0 || y >= image.Height || y < 0 || this.Contains(x, y) || image.GetPixel(x, y).ToArgb() != Form1.Black) { return; } this.Include(x, y); FloodFillPrivate(image, x, y - 1, level + 1); FloodFillPrivate(image, x, y + 1, level + 1); FloodFillPrivate(image, x - 1, y, level + 1); FloodFillPrivate(image, x + 1, y, level + 1); } internal BlackContent(int x, int y) { this.left = x; this.right = x; this.top = y; this.bottom = y; } internal void Include(int x, int y) { if (x < this.left) { this.left = x; } if (this.right < x) { this.right = x; } if (this.bottom < y) { this.bottom = y; } if (y < this.top) { this.top = y; } } internal bool Contains(int x, int y) { return !(x < this.left || x > this.right || y < this.top || y > this.bottom); } int left; internal int Left { get { return this.left; } } int top; internal int Top { get { return this.top; } } int right; internal int Right { get { return this.right; } } int bottom; internal int Bottom { get { return this.bottom; } } internal int Area { get { return Width * Height; } } internal int Width { get { return (this.right - this.left + 1); } } internal int Height { get { return (this.bottom - this.top + 1); } } } } 

我用ProgressBar观察了性能。 这个比较快。 我还必须提到你的图像太大了。

解决方案可能是找到黑色像素的区域。 找到黑色像素时,检查相邻像素的颜色并创建黑色像素区域。 当区域足够大时,可以将其视为内容。 下面的伪代码说明了这一点。 但它是一种资源密集型解决方案,至少应该进行优化。

  private List> areas = new List>(); public void PopulateAreas() { //Find all the areas for (var y = 0; y < yMax; y += 3) { for (var x = 0; x < xMax; x += 3) { Color col = bmp.GetPixel(x, y); if (col.ToArgb() == Color.Black.ToArgb()) { //Found a black pixel, check surrounding area var area = new List(); area.Add(new Point(x, y)); areas.Add(area); AppendSurroundingPixelsToArea(area, x, y); } } } var startX = Int32.MaxValue; var startY = Int32.MaxValue; var endX = Int32.MinValue; var endY = Int32.MinValue; //Loop through list of areas. foreach (var area in areas) { //Minimum size of area if (area.Count > 5) { var minx = area.Min(p => pX); if (area.Min(p => pX) < startX) startX = minx; //Do the same for the others... } } } public void AppendSurroundingPixelsToArea(List area, int startX, int startY) { for(var x = startX - 1; x <= startX + 1; x++) for (var y = startY - 1; y <= startY + 1; y++) { if ((x != 0 || y != 0) && IsBlackPixel(bmp, x, y)) { //Add to the area if (PointDoesNotExistInArea(area, x, y)) { area.Add(new Point(x, y)); AppendSurroundingPixelsToArea(area, x, y); } } } } 

我有解决方案,

我建议“柯达成像专业人士”。 这是显示多页tiff文件的查看器。 具有许多function,如:注释,反转颜色,旋转图像……等,这些都是内置function。