C# - 从照片中识别黑点 [英] C# - Black points recognition from a photo

查看:654
本文介绍了C# - 从照片中识别黑点的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一些带有黑点的白页照片,如下所示:
照片(点数不是很圆,我可以画得更好),
我会找到这些点的坐标。
我可以将图像二值化(上一张照片二值化:图片),但是我怎样才能找到这些黑点的坐标?我只需要每个点的一个像素的坐标,即大致中心。



这是用于学校作业。

解决方案

可以找到处理图像数据的基础知识在其他问题中,所以我不会详细介绍这个问题,但具体来说,对于阈值检查,我会通过收集红色,绿色和蓝色字节来实现每个像素(如我链接的答案中所示),然后将它们组合成颜色c = Color.FromArgb(r,g,b)并测试它是黑暗使用 c.GetBrightness()< brightnessThreshold 。值为0.4是测试图像的良好阈值。



您应该将此阈值检测的结果存储在一个数组中,其中每个项目都是一个值,指示是否阈值检查通过或失败。这意味着您可以使用与原始图像的高度和宽度一样简单的二维布尔数组。



如果你已经有了做所有这些的方法,那就更好了。只要确保你有一些数组,你可以很容易地查找二值化的结果。如果你的方法给你的结果是图像,你将更有可能最终得到一个简单的一维字节数组,但你的查找将只是一个像 imagedata [y *]的格式stride + x] 。这在功能上与二维数组中的内部查找相同,因此效率不会低。






现在,正如我在评论中所说的那样,这里的真实内容将是一种算法,用于检测哪些像素应该组合成一个blob。



此算法的一般用法是循环覆盖图像上的每个像素,然后检查A)是否清除了阈值,B)它是否已经存在于您现有的某个检测到的斑点中。如果像素符合条件,则生成连接到此像素的所有阈值传递像素的新列表,并将该新列表添加到检测到的blob列表中。我使用 Point 类来收集坐标,使每个blob成为 List< Point> ,以及我的blob a 列表< List< Point>>



至于算法本身,你要做的是make两个积分。一个是您正在构建的相邻点的完整集合(点列表),另一个是您正在扫描的当前边缘(当前边缘列表)。 当前边缘列表将开始包含您的原点,只要您的当前边缘列表中有项目,以下步骤就会循环显示:




  • 当前边缘列表中的所有项目添加到完整的积分列表

  • 为您的下一个边缘创建一个新的集合(下一个边缘列表)。

  • 对于当前边缘列表中的每个点,获取其直接相邻点的列表(不包括任何超出图像范围的点),并检查所有这些点是否清除阈值,以及它们是否已经在点列表下一个边列表。将通过检查的点数添加到下一个边缘列表

  • 当前边缘列表的此循环结束后,替换下一个边缘列表的原始当前边缘列表



...并且,正如我所说,只要在最后一步之后当前边缘列表不为空,就会循环执行这些步骤。



这将创建一个边缘扩展直到它匹配所有阈值清除像素,并将它们全部添加到列表中。最终,由于所有相邻像素最终都在主列表中,新生成的边缘列表将变为空,算法将结束。然后将新的点列表添加到blob列表中,之后循环的任何像素都可以被检测为已存在于这些blob中,因此不会为它们重复算法。



有两种方法可以做邻近点;你得到它周围的四个点,或者全部八个点。不同的是,使用四个不会使算法做对角线跳跃,而使用八个会。 (另一个影响是,一个导致算法以菱形扩展,而另一个扩展为正方形。)因为你的blob周围似乎有一些杂散像素,所以我建议你得到所有八个。



史蒂夫在答案中指出 ,这是一种非常快速的检查方式查看集合中是否存在点是创建具有图像尺寸的二维布尔数组,例如 Boolean [,] inBlob = new Boolean [height,width]; ,它与实际的点列表保持同步。因此,无论何时添加点,您还要将布尔数组中的 [y,x] 位置标记为 true 。这将对 if(collection.contains(point))类型进行相当大量的检查,就像 if(inBlob [y,x]),这需要根本没有迭代



我有一个 List< Boolean [, ] GT; inBlobs 我与列表< List< Point>>保持同步blob 我构建了,并且在扩展边缘算法中,我为下一个边缘列表保留了布尔[,] 积分列表(后者在最后添加到 inBlobs )。



正如我评论的那样,一旦你获得了blob,只需在每个blob上遍历它们内部的点,并获得X和Y的最小值和最大值,这样就可以得到blob的边界。然后只取这些平均值来获得blob的中心。



额外费用:




  • 如果所有的点都保证相距很远,那么摆脱浮动边缘像素的一种非常简单的方法是获取每个blob的边界,将它们全部展开一定的阈值(I取了2个像素,然后循环遍历这些矩形并检查是否有任何相交,并合并那些。 Rectangle 类具有 IntersectsWith()以便于检查,还有一个静态 Rectangle。膨胀以增加矩形的大小。


  • 您可以通过仅存储边缘点来优化填充方法的内存使用量(阈值主列表中与四个主要方向中的任何一个中的非匹配邻居匹配的点。最终的边界,也就是中心,将保持不变。需要记住的重要一点是,当你从blob列表中排除一堆点时,你应该布尔[,] 用于检查已处理像素的数组。这不会占用任何额外的内存。




完整的算法,包括优化,对你的照片采取行动,使用0.4作为亮度阈值:





蓝色是检测到的斑点,红色是检测到的轮廓(通过使用内存优化方法),单个绿色像素表示所有斑点的中心点。 / p>

I have some photos of white pages with some black points drawn on, like this: photo (the points aren't very circular, I can draw them better), and I would find the coordinates of these points. I can binarize the images (the previous photo binarized: image), but how can I find the coordinates of these black points? I need only the coordinates of one pixel for each point, the approximate center.

This is for a school assignment.

解决方案

The basics for processing image data can be found in other questions, so I won't go into deeper detail about that, but for the threshold check specifically, I'd do it by gathering the red, green and blue bytes of each pixel (as indicated in the answer I linked), and then just combine them to a Color c = Color.FromArgb(r,g,b) and testing that to be "dark" using c.GetBrightness() < brightnessThreshold. A value of 0.4 was a good threshold for your test image.

You should store the result of this threshold detection in an array in which each item is a value that indicates whether the threshold check passed or failed. This means you can use something as simple as a two-dimensional Boolean array with the original image's height and width.

If you already have methods of doing all that, all the better. Just make sure you got some kind of array in which you can easily look up the result of that binarization. If the method you have gives you the result as image, you will be more likely to end up with a simple one-dimensional byte array, but then your lookups will simply be of a format like imagedata[y * stride + x]. This is functionally identical to how internal lookups in a two-dimensional array happen, so it won't be any less efficient.


Now, the real stuff in here, as I said in my comment, would be an algorithm to detect which pixels should be grouped together to one "blob".

The general usage of this algorithm is to loop over every single pixel on the image, then check if A) it cleared the threshold, and B) it isn't already in one of your existing detected blobs. If the pixel qualifies, generate a new list of all threshold-passed pixels connected to this one, and add that new list to your list of detected blobs. I used the Point class to collect coordinates, making each of my blobs a List<Point>, and my collection of blobs a List<List<Point>>.

As for the algorithm itself, what you do is make two collections of points. One is the full collection of neighbouring points you're building up (the points list), the other is the current edge you're scanning (the current edge list). The current edge list will start out containing your origin point, and the following steps will loop as long as there are items in your current edge list:

  • Add all items from the current edge list into the full points list.
  • Make a new collection for your next edge (the next edge list).
  • For each point in your current edge list, get a list of its directly neighbouring points (excluding any that would fall outside the image bounds), and check for all of these points if they clear the threshold, and if they are not already in either the points list or the next edge list. Add the points that pass the checks to the next edge list.
  • After this loop through the current edge list ends, replace the original current edge list by the next edge list.

...and, as I said, loop these steps as long as your current edge list after this last step is not empty.

This will create an edge that expands until it matches all threshold-clearing pixels, and will add them all to the list. Eventually, as all neighbouring pixels end up in the main list, the new generated edge list will become empty, and the algorithm will end. Then you add your new points list to the list of blobs, and any pixels you loop over after that can be detected as already being in those blobs, so the algorithm is not repeated for them.

There are two ways of doing the neighbouring points; you either get the four points around it, or all eight. The difference is that using four will not make the algorithm do diagonal jumps, while using eight will. (An added effect is that one causes the algorithm to expand in a diamond shape, while the other expands in a square.) Since you seem to have some stray pixels around your blobs, I advice you to get all eight.

As Steve pointed out in his answer, a very quick way of doing checks to see if a point is present in a collection is to create a two-dimensional Boolean array with the dimensions of the image, e.g. Boolean[,] inBlob = new Boolean[height, width];, which you keep synchronized with the actual points list. So whenever you add a point, you also mark the [y, x] position in the Boolean array as true. This will make rather heavy checks of the if (collection.contains(point)) type as simple as if (inBlob[y,x]), which requires no iterations at all.

I had a List<Boolean[,]> inBlobs which I kept synced with the List<List<Point>> blobs I built, and in the expanding-edge algorithm I kept such a Boolean[,] for both the next edge list and the points list (the latter of which was added to inBlobs at the end).

As I commented, once you have your blobs, just loop over the points inside them per blob and get the minimums and maximums for both X and Y, so you end up with the boundaries of the blob. Then just take the averages of those to get the center of the blob.

Extras:

  • If all your dots are guaranteed to be a significant distance apart, a very easy way to get rid of floating edge pixels is to take the edge boundaries of each blob, expand them all by a certain threshold (I took 2 pixels for that), and then loop over these rectangles and check if any intersect, and merge those that do. The Rectangle class has both an IntersectsWith() for easy checks, and a static Rectangle.Inflate for increasing a rectangle's size.

  • You can optimise the memory usage of the fill method by only storing the edge points (threshold-matching points with non-matching neighbours in any of the four main directions) in the main list. The final boundaries, and thus the center, will remain the same. The important thing to remember then is that, while you exclude a bunch of points from the blob list, you should mark all of them in the Boolean[,] array that's used for checking the already-processed pixels. This doesn't take up any extra memory anyway.

The full algorithm, including optimisations, in action on your photo, using 0.4 as brightness threshold:

Blue are the detected blobs, red is the detected outline (by using the memory-optimised method), and the single green pixels indicate the center points of all blobs.

这篇关于C# - 从照片中识别黑点的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆