繁体   English   中英

计算图像中任意颜色质心并将其提供给 PHP 的最快方法

[英]Fastest way to compute & feed an arbitrary color centroid in an image to PHP

我正在寻找基于图像中任意颜色计算方向矢量的最快方法(Rpi 相机,但现在可以使用 JPEG 文件进行测试),也就是跟踪彩球项目。 请注意,生成的向量(或质心坐标,无论如何)需要传递给 PHP 以执行程序,所以我正在寻找的解决方案需要以 PHP 结尾,但可以是之前的任何东西,因为它可以在Windows 和 Linux。

考虑输入 JPEG 图像:

在此处输入图像描述

这是我所追求的 2 个示例方向向量,它们是基于 1)蓝绿色输入和 2)紫色输入获得的。 显然,一次只会询问 1 个向量,我将 2 个用于在 1 个图像中演示多个示例,但一次总是只有 1 个向量。 请注意,生成的向量(“v”)被标准化为 -1.0(底部/左侧)到 +1.0(底部/右侧),因此零是图片的中间。

在此处输入图像描述

以下是我迄今为止实施/测试的各种解决方案以及整个过程需要多少时间,基于 960x640 JPEG 图片,但实施的解决方案将与 Rpi 相机输入相关联,我还没有相机所以在相机从中国运到之前,我使用 JPEG 图像。

1) 2700ms : Use GD2 that is bundled with PHP, for loop over each pixels, push pixels matching ~10% RGB values in XY arrays, average the XY arrays, compute/normalize directional vector from XY arrays.

$arr_matching_pixels = array('arr_x' => array(), 'arr_y' => array());
for($y = 0; $y < $h - 1; $y++){
    for($x = 0; $x < $w - 1; $x++){
        $arr_pixel = imagecolorsforindex($img, imagecolorat($img, $x, $y));
        if(abs($arr_pixel['red'] - $arr_seek_color['red']) < 30){
            if(abs($arr_pixel['green'] - $arr_seek_color['green']) < 30){
                if(abs($arr_pixel['blue'] - $arr_seek_color['blue']) < 30){
                    array_push($arr_matching_pixels['arr_x'], $x);
                    array_push($arr_matching_pixels['arr_y'], $y);
                }
            }
        }
    }
}
// Compute centroid of color... etc...

2) 700ms : 与 #1 相同,除了首先使用imagecreatefromjpeg('_test_cam_img.jpg');

3) 560ms : 与 #2 相同,除了使用带有像素迭代器循环的 ImageMagick 来读取像素

$imagick = new Imagick(realpath($o_img));
$arr_matching_pixels = array('arr_x' => array(), 'arr_y' => array());
$arr_pixel = array();
$iterator = $imagick->getPixelIterator();
foreach($iterator as $y => $pixels){
    foreach($pixels as $x => $pixel){
        $arr_pixel = $pixel->getColor();
        if(abs($arr_pixel['r'] - $arr_seek_color['red']) < 30){
            if(abs($arr_pixel['g'] - $arr_seek_color['green']) < 30){
                if(abs($arr_pixel['b'] - $arr_seek_color['blue']) < 30){
                    array_push($arr_matching_pixels['arr_x'], $x);
                    array_push($arr_matching_pixels['arr_y'], $y);
                }
            }
        }
    }
}
// Compute centroid of color... etc...

4) 340ms : 通过 exec() function 调用系统的 ImageMagick 二进制文件,将图像位置、色度/颜色键、50% 参数调整大小、10% 模糊颜色提取参数和修饰符传递给它: a textual (CSV-like) list representation of desired pixels, then use PHP to loop over each line, explode commas and push all pixels in XY arrays, average the XY arrays, compute/normalize directional vector from XY arrays. 我注意到调用 exec() 证明比直接从 Windows 命令行执行相同的命令要慢得多。

$imagick = new Imagick(realpath($o_img));
$out = exec('"E:\Users\Ben\Roaming Apps\imagemagick-6.9.3\convert" E:\wamp64\www\test_cam_img.jpg -resize 50% -fuzz 10% +transparent rgb(' . $arr_seek_color['red'] . ',' . $arr_seek_color['green'] . ',' . $arr_seek_color['blue'] . ') sparse-color:');
$arr_lines = explode(' ', $out);
$arr_matching_pixels = array('arr_x' => array(), 'arr_y' => array());
foreach($arr_lines as $str_line){
    $arr_xy_coords = explode(',', $str_line);
    array_push($arr_matching_pixels['arr_x'], $arr_xy_coords[0]);
    array_push($arr_matching_pixels['arr_y'], $arr_xy_coords[1]);
}
// Compute centroid of color... etc...

5) 32ms : PHP 创建一个包含图像路径和色度/颜色键的“输入”文本文件并开始循环,直到它读取一个“输出”文本文件。 python+OpenCV 脚本已经/总是运行一个(可停止的)无限循环,不断寻找一个“in”文本文件,当它存在时,它会读取它,分解值,使用 HSV 值 ~10% 制作一个 1 位掩码(cv2.inRange) 来自“in”文件,然后使用 cv2.findNonZero(mask) 创建一个数组并计算数组平均值并将其写入 PHP 立即读取的“out”文本文件,其中包含方向向量值。 这是迄今为止我发现的最快的方法,但它很尴尬,因为这意味着 python 脚本必须在 CRONJOB 中进行编程,并在它崩溃时在单个实例中进行监控/重新启动。

file_put_contents('_avg_color_coords_in.txt', $o_img . "\n" . $arr_seek_color['h'] . ',' . $arr_seek_color['s'] . ',' . $arr_seek_color['l']);

$starttime = time();
while((time() - $starttime) < 5){ // Max 5 seconds (exaggerated)
    if(file_exists('_avg_color_coords_out.txt')){
        $dir_vector = (float) file_get_contents('_avg_color_coords_out.txt');
        if(!@unlink('_avg_color_coords_out.txt')){
            sleep(1);
            unlink('_avg_color_coords_out.txt');
        }
        break;
    }
    usleep(2000);
}
// $dir_vector ("v", the centroid of the color) is already computed by Python


// ---------- PYTHON SCRIPT ----------
import math
import cv2
import numpy as np
import os
import time

#cap = cv2.VideoCapture(0)

#while (1):
#    _, frame = cap.read()
if(os.path.exists('_avg_color_coords_stop.txt')):
    exit()
while not os.path.exists('_avg_color_coords_in.txt'):
    time.sleep(0.002)
f = open('_avg_color_coords_in.txt', 'r')
imgsrc = f.readline().rstrip('\n')
rgbcol = [int(x) for x in f.readline().rstrip('\n').split(',')]
frame = cv2.imread(imgsrc)
h, w = frame.shape[:2]

hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
hfacl = rgbcol[0] / 360 * 180 * 0.95
hfach = rgbcol[0] / 360 * 180 * 1.05
sfacl = rgbcol[1] / 100 * 255 * 0.9
sfach = rgbcol[1] / 100 * 255 * 1.1
vfacl = rgbcol[2] / 100 * 255 * 0.9
vfach = rgbcol[2] / 100 * 255 * 1.1
lower_color = np.array([hfacl, sfacl, vfacl]) # 0..180, 0..255, 0..255 not percentage!
upper_color = np.array([hfach, sfach, vfach]) # 0..180, 0..255, 0..255 not percentage!
mask = cv2.inRange(hsv, lower_color, upper_color)
#cv2.imshow('mask', mask)

points = cv2.findNonZero(mask)
if(points.any()):
    avg = np.mean(points, axis=0)
else:
    avg = [0,0]
#print(avg)

v = -math.atan(((w * 0.5) - avg[0][0]) / (h - avg[0][1])) / (3.1415 * 0.5);
f2 = open('_avg_color_coords_out.txt', 'w+')
f2.write("%s" % str(v))

#    k = cv2.waitKey(5) & 0xff
#    if k == 27:
#        break

#cv2.destroyAllWindows()
#cap.release()

f2.close()
f.close()
os.remove('_avg_color_coords_in.txt')

6) 38ms : 与 #5 相同,只是首先将 canvas 的大小调整 50%(可接受的损失),这似乎根本没有加快速度,甚至似乎有点适得其反。

有没有更快的方法或者这是最优的? 这将在 900mhz Rpi 上每秒运行一次,因此需要快速。 我认为 900mhz CPU 上的 30ms 将在 150-200ms 左右(尚未测试,等待相机发货)

我在php-vips 中有一个快速的 go

#!/usr/bin/env php
<?php

require __DIR__ . '/vendor/autoload.php';

use Jcupitt\Vips;

$image = Vips\Image::newFromFile($argv[1], ['access' => 'sequential']);

# Target colour in RGB.
$target = [50, 10, 100];

# Select pixels where all bands are less than 10 away from the target.
# (and render it to memory ... we'll be reusing this mask image).
# The mask image will have one band with 0 for false and 255 for true.
$mask = $image->subtract($target)->abs()->less(10)->bandand()->copyMemory();

# The number of set pixels in the mask.
$n_set = $mask->avg() * $mask->width * $mask->height / 255;

# Handy for debugging: uncomment to write the mask image for inspection.
# $mask->writeToFile("x.png");

# Make a two-band image where band 0 is x coordinates and band 1 is y
# coordinates.
$coords = Vips\Image::xyz($mask->width, $mask->height);

# Make an indexed histogram: sum $coords at each position.
$pos = $coords->hist_find_indexed($mask);

# fetch the sum of the 255 value (true) pixels
[$x_sum, $y_sum] = $pos->getpoint(255, 0);

echo("x = " . $x_sum / $n_set . "\n");
echo("y = " . $y_sum / $n_set . "\n");

我可以这样运行它:

$ time ./locate-rgb.php ~/pics/x.jpg
x = 483.375
y = 487.75
real    0m0.079s
user    0m0.085s
sys 0m0.022s

所以在这台普通的笔记本电脑上大约需要 80 毫秒。 这包括 PHP 启动和关闭,以及解压缩 JPG 图像。

这只适用于非常受限的照明和相机设置,但也许没关系? 让球检测更漂亮很容易,但当然会减慢一点。

正如我在评论中所说,目前还不清楚您要做什么,或者为什么必须在 PHP 中完成。

目前尚不清楚您为什么说您使用的是 JPEG,但您给了我们一个 PNG,但如果速度对您很重要,那么您可能正在使用视频。 使用 PNG 和 JPEG 和视频之间的区别可能超过处理所需的时间。 使用 JPEG 导致的伪影和颜色信息丢失可能会产生重大影响。

如果您说“我想以尽可能高的帧速率获取视频并跟踪球”并扩展限制是什么以及您真正在做什么以及一切如何连接以及需要做的频率,您将获得比抱怨通过迭代 PHP 中的像素来生成一些定义不明确的“矢量”的时间要好得多,这总是一场灾难。

Eric 建议您使用 MMAL 进行 JPEG 处理,但您的最终系统可能不涉及 JPEG。

在对您实际想要实现的目标进行一个不错的概述之前,我猜您实际上想知道球的质心,但是您再次只谈论传递 X,Y 坐标的列表。

与其传递大量坐标列表,不如让 ImageMagick 计算得出球质心的力矩:

convert garden.jpg \
   -fuzz 10% -fill white -opaque "srgb(98,40,245)" \
   +fuzz     -fill black +opaque white             \
   -colorspace gray -verbose -moments info:

样品 Output

  Format: JPEG (Joint Photographic Experts Group JFIF format)
  Mime type: image/jpeg
  Class: DirectClass

  ...
  ...
  Channel moments:
    Gray:
      Centroid: 897.842,585.948                         <<< CENTROID OF WHITE AREA
      Ellipse Semi-Major/Minor axis: 14.9801,14.3506
      Ellipse angle: 2.17802
      Ellipse eccentricity: 0.205004
      Ellipse intensity: 252.977 (0.992066)
      I1: 0.000629708 (0.160576)
      I2: 7.30093e-10 (4.74743e-05)
      ...
      ...

在此处输入图像描述

另请注意,如果您解释了您在做什么并展示了您的代码,您也会得到更好的答案。 例如,这里是计算颜色时刻的时间

time convert garden.jpg -fill white -fuzz 10% -opaque "srgb(98,40,245)" +fuzz -fill black +opaque white -verbose -moments info: > /dev/null

real    0m0.401s
user    0m4.161s
sys     0m0.050s

但去灰度剃须30%的时间:

time convert garden.jpg -fill white -fuzz 10% -opaque "srgb(98,40,245)" +fuzz -fill black +opaque white -colorspace gray -verbose -moments info: > /dev/null

real    0m0.327s
user    0m3.383s
sys     0m0.058s

将图像缩放 50% 会产生更大的差异,进一步减少 65% 的时间:

time convert garden.jpg -scale 50% -fill white -fuzz 10% -opaque "srgb(98,40,245)" +fuzz -fill black +opaque white -colorspace gray -verbose -moments info: > /dev/null

real    0m0.100s
user    0m0.433s
sys     0m0.011s

这正是您需要具体并显示您的代码的原因。 您可能正在使用resize而不是scale - 我不知道。

如果 Python 和 OpenCV 是最快的(虽然你没有显示你的代码,所以不清楚你在做什么)你可以开发这个想法并通过通过ZE111446745A7AE1838再次交换图像和数据来提高速度。仍然不清楚你到底在做什么。 当您可能只是对 HSL 数据进行阻塞 POP 并从 Redis 得到结果时,您当前可能会不必要地抨击等待文件出现的文件系统。 但我不能确定。 如果我看到你的代码,我可能会建议获取到 RAMdisk,但我现在还不能说。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM