图像识别 php

tech2022-08-30  113

图像识别 php

I recently stumbled across a fascinating question: how could I tell whether an image had changed significantly? As PHP developers, the most troublesome image problem we have to deal with is how to resize an upload with an acceptable loss of quality.

我最近偶然发现了一个有趣的问题:如何判断图像是否发生了重大变化? 作为PHP开发人员,我们必须处理的最麻烦的图像问题是如何以可接受的质量损失来调整上载的大小。

In the end I discovered what many before me have – that this problem becomes relatively simple given the application of some fundamental mathematical principles. Come along with me as we learn about them…

最后,我发现了我之前拥有的许多知识–在应用一些基本数学原理的情况下,这个问题变得相对简单。 跟我们一起了解我们...

You can find the code for this tutorial at https://github.com/undemanding/difference.

您可以在https://github.com/undemanding/difference上找到本教程的代码。

位图 (Bitmaps)

There are two popular ways of thinking about images. The first is as a grid of individual pixels, composed of varying levels of color and contrast. Commonly, we break these colors down into their constituent red, green, and blue values. We could also think of them as hue, saturation, and lightness.

图像有两种流行的思考方式。 第一种是由各个像素组成的网格,由不同级别的颜色和对比度组成。 通常,我们将这些颜色分解为红色,绿色和蓝色的值。 我们还可以将它们视为色相,饱和度和亮度。

The second way of thinking about images is in terms of vectors. A line isn’t the pixels in between but rather a starting point and an ending point, with some meta data that describes a stroke in between. We’re going to focus on bitmaps because they’ll make the whole process easier.

关于图像的第二种思考方式是矢量。 一条线不是中间的像素,而是起点和终点,其中一些元数据描述了中间的笔画。 我们将重点放在位图上,因为它们会使整个过程变得更容易。

We can break any image down into this bitmap grid, with code resembling:

我们可以将任何图像分解为该位图网格,其代码类似于:

$image = imagecreatefrompng($path); $width = imagesx($image); $height = imagesy($image); $map = []; for ($y = 0; $y < $height; $y++) { $map[$y] = []; for ($x = 0; $x < $width; $x++) { $color = imagecolorat($image, $x, $y); $map[$y][$x] = [ "r" => ($color >> 16) & 0xFF, "g" => ($color >> 8) & 0xFF, "b" => $color & 0xFF ]; } }

Given the width and height of the image, we can use a function called imagecolorat (on an image resource) to get a single integer value for the red, green, and blue at that pixel. We can then use bit shifting and masking to get the individual values of each from the single integer value.

给定图像的宽度和高度,我们可以使用一个名为imagecolorat的函数(在图像资源上)来获取该像素处红色,绿色和蓝色的单个整数值。 然后,我们可以使用移位和屏蔽从单个整数值中获取每个值。

Each red, green, and blue value is in a range from 0 to 255. In binary, this range can be expressed as 00000000 to 11111111. A single integer value can represent 3 sets of these binary values and bit shifting is a way to get the binary values from the first, second, or third group of 8 bits.

红色,绿色和蓝色的每个值都在0到255 。 以二进制形式,此范围可以表示为00000000到11111111 。 单个整数值可以表示3组这些二进制值,而移位是从第一组,第二组或第三组8位中获取二进制值的一种方式。

If we create these grids from a couple of images; how can we compare them? We got the answer 2300 years ago…

如果我们从几个图像创建这些网格; 我们如何比较它们? 2300年前,我们得到了答案……

三维距离 (Distance In Three Dimensions)

Can you remember how to calculate the length of a line? Any line you can draw on paper can be thought of as the hypotenuse of a triangle (the long side). To measure it, we can square the horizontal and vertical sides of the right-angle triangle the hypotenuse makes, and work out their combined square root:

您还记得如何计算线的长度吗? 您可以在纸上绘制的任何线条都可以视为三角形的斜边(长边)。 为了测量它,我们可以将斜边形成的直角三角形的水平和垂直边平方,然后算出它们的组合平方根:

$start = [$x = 10, $y = 15]; $end = [$x = 20, $y = 30]; $width = $end[0] - $start[0]; $width *= $width; $height = $end[1] - $start[1]; $height *= $height; $distance = sqrt($width + $height); // ≈ 18.03

If the line was three-dimensional, we’d have to add a third component to the equation. There’s a general mathematical principle for this kind of distance measurement, called Euclidean distance. Older forms of it were called Pythagorean metric, because of the close relationship to the hypotenuse calculation we just did.

如果线是三维的,则必须在方程中添加第三个分量。 这种距离测量有一个通用的数学原理,称为欧几里得距离 。 它的较旧形式被称为勾股定律,因为它与我们刚才做的斜边计算有密切关系。

The formula expands to as many dimensions as we want it to, but we only need it for three:

该公式可以扩展到我们想要的任意维度,但是我们只需要三个维度即可:

$first = [$red = 100, $green = 125, $blue = 150]; $second = [$red = 125, $green = 150, $blue = 175]; $red = $second[0] - $first[0]; $red *= $red; $green = $second[1] - $first[1]; $green *= $green; $blue = $second[2] - $first[2]; $blue *= $blue; $distance = sqrt($red + $green + $blue); // ≈ 43.30

We can apply this principle to every pixel of the bitmaps, until we have a third bitmap of just the differing values. Let’s try it…

我们可以将此原理应用于位图的每个像素,直到我们得到仅具有不同值的第三位图。 让我们尝试一下...

简单的图像差异 (Simple Image Differences)

We can apply these principles with very little code. Let’s make a class to load images, create their bitmaps and calculate a map of pixel differences:

我们可以用很少的代码来应用这些原理。 让我们创建一个类来加载图像,创建其位图并计算像素差异图:

class State { private $width; private $height; private $map = []; public function __construct($width, $height) { $this->width = $width; $this->height = $height; } }

Each map has a defined width and height. To populate the map, we need to load images into memory:

每个地图都有定义的宽度和高度。 要填充地图,我们需要将图像加载到内存中:

private static function createImage($path) { $image = null; $info = getimagesize($path); $type = $info[2]; if ($type == IMAGETYPE_JPEG) { $image = imagecreatefromjpeg($path); } if ($type == IMAGETYPE_GIF) { $image = imagecreatefromgif($path); } if ($type == IMAGETYPE_PNG) { $image = imagecreatefrompng($path); } if (!$image) { throw new InvalidArgumentException("image invalid"); } return $image; }

We can use the GD image library to read a number of image formats. This function tries to load a file path that could be a JPEG, a GIF, or a PNG. If none of these work, we can just raise an exception. Why is this method static? We’re going to use it in another static method:

我们可以使用GD图像库读取多种图像格式。 此函数尝试加载可能是JPEG,GIF或PNG的文件路径。 如果这些都不起作用,我们可以提出一个例外。 为什么此方法是静态的? 我们将在另一个静态方法中使用它:

public static function fromImage($path) { if (!file_exists($path)) { throw new InvalidArgumentException("image not found"); } $image = static::createImage($path); $width = imagesx($image); $height = imagesy($image); $map = []; for ($y = 0; $y < $height; $y++) { $map[$y] = []; for ($x = 0; $x < $width; $x++) { $color = imagecolorat($image, $x, $y); $map[$y][$x] = [ "r" => ($color >> 16) & 0xFF, "g" => ($color >> 8) & 0xFF, "b" => $color & 0xFF ]; } } $new = new static($width, $height); $new->map = $map; return $new; }

This static function allows us to create new image states (or maps) from a static call to State::fromImage("/path/to/image.png"). If the path doesn’t exist, we can raise another exception. Then we have the same image grid construction logic we saw previously. Finally, we create a new State, with a defined width, height and the map we constructed.

通过此静态函数,我们可以从对State::fromImage("/path/to/image.png")的静态调用中创建新的图像状态(或地图State::fromImage("/path/to/image.png") 。 如果路径不存在,我们可以引发另一个异常。 然后,我们具有与之前看到的相同的图像网格构造逻辑。 最后,我们创建一个新的State ,具有定义的宽度,高度和我们构建的地图。

Now, let’s make a way to compare multiple images:

现在,让我们来比较多个图像的方法:

public function withDifference(State $state, callable $method) { $map = []; for ($y = 0; $y < $this->height; $y++) { $map[$y] = []; for ($x = 0; $x < $this->width; $x++) { $map[$y][$x] = $method( $this->map[$y][$x], $state->map[$y][$x] ); } } return $this->cloneWith("map", $map); } private function cloneWith($property, $value) { $clone = clone $this; $clone->$property = $value; return $clone; }

We go through each pixel, in each image. We pass them to a difference function and assign the resulting value to a new map. Finally, we create a clone of the current State, so it keeps the width and height, but sets a new state. This ensures we don’t modify existing State instances, but rather have access to a new instance with its own map.

我们遍历每个图像中的每个像素。 我们将它们传递给差分函数,并将结果值分配给新地图。 最后,我们创建当前State的副本,以便它保留宽度和高度,但设置一个新状态。 这样可以确保我们不修改现有的State实例,而是可以使用其自己的地图访问新实例。

What does that difference function look like?

这种差异功能是什么样的?

class EuclideanDistance { public function __invoke(array $p, array $q) { $r = $p["r"] - $q["r"]; $r *= $r; $g = $p["g"] - $q["g"]; $g *= $g; $b = $p["b"] - $q["b"]; $b *= $b; return sqrt($r + $g + $b); } }

It turns out we can use classes as functions, if we give them an __invoke method. In this class we can put the Euclidian distance logic we saw previously. We can put all of these pieces together like this:

事实证明,如果我们给它们提供__invoke方法,则可以将类用作函数。 在这一课中,我们可以介绍我们先前看到的欧几里得距离逻辑。 我们可以将所有这些片段放在一起,如下所示:

$state1 = State::fromImage("/path/to/image1.png"); $state2 = State::fromImage("/path/to/image2.png"); $state3 = $state1->withDifference( $state2, new EuclideanDistance() );

This method works great for images that are almost identical. When we try to work out the differences in very similar photos or even lossy versions of almost identical images, we’re presented with many slight differences.

该方法适用于几乎相同的图像。 当我们尝试计算出非常相似的照片甚至几乎相同图像的有损版本中的差异时,我们会看到许多细微的差异。

标准偏差 (Standard Deviation)

To get around this problem, we need to remove the noise and focus only on the biggest problem. We can do that by working out how spread out the differences are. This measure of how spread out numbers are is called Standard Deviation.

为了解决这个问题,我们需要消除噪音,只关注最大的问题。 我们可以通过找出差异的程度来做到这一点。 衡量数字分布的方法称为标准偏差 。

Image from Wikipedia

图片来自维基百科

Most of the small differences between images are within the standard deviation (or the dark blue band between -1σ and 1σ). If we eliminate all the small differences within the standard deviation, then we should be left with the big differences. To work out which pixels are within the standard deviation, we need to work out the average pixel value:

图像之间的大多数小差异都在标准偏差之内(或-1σ和1σ之间的深蓝色带)。 如果我们消除了标准偏差内的所有小差异,那么我们应该留有大差异。 要计算出哪些像素在标准偏差内,我们需要计算出平均像素值:

public function average() { $average = 0; for ($y = 0; $y < $this->height; $y++) { for ($x = 0; $x < $this->width; $x++) { if (!is_numeric($this->map[$y][$x])) { throw new LogicException("pixel is not numeric"); } $average += $this->map[$y][$x]; } } $average /= ($this->width * $this->height); return $average; }

Averages are easy! Just add together everything and divide by the number of things you added together. An average of two things is their total value divided by two. An average of 400 x 300 things is their total value divided by 120,000.

平均很容易! 只需将所有内容加在一起,然后除以您加在一起的数量即可。 平均两件事是它们的总价值除以二。 平均400 x 300事物的总价值除以120,000。

Notice how we’re expecting numeric values during the calculation? That means we first need to generate the purely numeric state ($state3 in the above example), or a state generated by the EuclideanDistance difference function.

注意我们在计算过程中期望数值如何? 这意味着我们首先需要生成纯数字状态(在上面的示例中为$state3 ),或者由EuclideanDistance差函数生成的状态。

public function standardDeviation() { $standardDeviation = 0; $average = $this->average(); for ($y = 0; $y < $this->height; $y++) { for ($x = 0; $x < $this->width; $x++) { if (!is_numeric($this->map[$y][$x])) { throw new LogicException( "pixel is not numeric" ); } $delta = $this->map[$y][$x] - $average; $standardDeviation += ($delta * $delta); } } $standardDeviation /= (($this->width * $this->height) - 1); $standardDeviation = sqrt($standardDeviation); return $standardDeviation; }

This calculation is a little similar to the distance calculation we did before. The basic idea is that we work out the average of all the pixels. We then work out how far each is from that average, and then work out the average of all of these distances.

此计算与我们之前进行的距离计算有点相似。 基本思想是计算出所有像素的平均值。 然后,我们计算每个距离与该平均值的距离,然后计算所有这些距离的平均值。

We can apply this back to the State:

我们可以将其应用于State :

public function withReducedStandardDeviation() { $map = array_slice($this->map, 0); $deviation = $this->standardDeviation(); for ($y = 0; $y < $this->height; $y++) { for ($x = 0; $x < $this->width; $x++) { if (abs($map[$y][$x]) < $deviation) { $map[$y][$x] = 0; } } } return $this->cloneWith("map", $map); }

This way, if differences are inside the standard deviation band, we exclude them from a new State. The result is a cleaner picture of changes.

这样,如果差异在标准偏差范围之内,我们就将其排除在新State 。 结果是对变化的清晰了解。

public function boundary() { $ax = $this->width; $bx = 0; $ay = $this->width; $by = 0; for ($y = 0; $y < $this->height; $y++) { for ($x = 0; $x < $this->width; $x++) { if ($this->map[$y][$x] > 0) { if ($x > $bx) { $bx = $x; } if ($x < $ax) { $ax = $x; } if ($y > $by) { $by = $y; } if ($y < $ay) { $ay = $y; } } } } if ($ax > $bx) { throw new LogicException("ax is greater than bx"); } if ($ay > $by) { throw new LogicException("ay is greater than by"); } $ax = ($ax / $this->width) * $this->width; $bx = ((($bx + 1) / $this->width) * $this->width) - $ax; $ay = ($ay / $this->height) * $this->height; $by = ((($by + 1) / $this->height) * $this->height) - $ay; return [ "left" => $ax, "top" => $ay, "width" => $bx, "height" => $by ]; }

The final piece of the puzzle is a function to work out where the boundaries of the changes are. We can use this function as a way to draw a box around the changes from one image to another. The box starts on the edges of the grid and slowly moves inward until it reaches changes in the grid.

难题的最后一部分是确定变更边界在何处的功能。 我们可以使用此功能在从一个图像到另一个图像的变化周围绘制一个框。 该框从网格的边缘开始,然后向内缓慢移动,直到达到网格中的变化为止。

结论 (Conclusion)

I started this experiment to find the differences between two images. You see, I wanted to take screenshots of an interface, during automated testing, and tell if something significant had changed.

我开始进行此实验以查找两个图像之间的差异。 您看到的是,我想在自动化测试过程中截取界面的屏幕截图,并判断是否有重大更改。

Euclidian distance, applied to bitmaps, told me about every changed pixel. Then I wanted to allow for slight changes (like small text or colour changes), so I applied Standard Deviation noise removal, so that only significant changes would come through. Finally, I could work out exactly how many pixels were different as a percentage of the total number of pixels on the screen. I could tell if one screenshot was within 10% or 20% of a test fixture image.

应用于位图的欧几里得距离告诉我每个像素的变化。 然后,我想允许进行细微的更改(例如小的文本或颜色更改),因此我应用了“标准偏差”噪声消除功能,以便仅进行重大更改。 最后,我可以精确计算出多少个像素在屏幕上占像素总数的百分比。 我可以判断一个屏幕截图是否在测试夹具图像的10%或20%之内。

Perhaps you have need of this for something wildly different. Perhaps you have ideas of how this can be improved. Let us know in the comments!

也许您需要此功能以实现截然不同的功能。 也许您对如何进行改进有想法。 让我们在评论中知道!

翻译自: https://www.sitepoint.com/finding-differences-in-images-with-php/

图像识别 php

相关资源:PHP实现百度人脸识别
最新回复(0)