Самый быстрый способ вычислить и передать произвольный цветовой центроид изображения в PHP - PullRequest
0 голосов
/ 31 октября 2019

Я ищу самый быстрый способ вычисления вектора направления на основе произвольного цвета в изображении (камера Rpi, но файл JPEG для тестирования в настоящее время в порядке), то есть отслеживание проекта с цветным шариком. Обратите внимание, что полученный вектор (или координаты центроида, что угодно) должен быть передан в PHP для выполнения программы , поэтому решение, которое я ищу, должно заканчиваться PHP, но до этого может быть что угодно,учитывая, что это может быть реализовано как в Windows, так и в Linux.

Рассмотрим входное изображение JPEG:

enter image description here

Вот 2 примера вектора направленияЯ после, получен на основе 1) ввода цвета чирка и 2) ввода фиолетового цвета. Очевидно, что за один раз будет задан только 1 вектор, я положил 2, чтобы продемонстрировать несколько примеров на 1 изображении, но это всегда будет только 1 вектор за раз. Обратите внимание, что результирующие векторы («v») стандартизированы от -1,0 (внизу / слева) до +1,0 (внизу / справа), так что ноль - это середина изображения.

enter image description here

Вот различные решения, которые я реализовал / протестировал до сих пор, и сколько времени занимает весь процесс, на основе изображения JPEG 960x640, но реализованное решение будет привязано к входу камеры Rpi, У меня еще нет камеры, поэтому я использую изображение JPEG, пока камера не прибудет из Китая.

1) 2700ms : использовать GD2, который поставляется в комплекте с PHP, для цикла по каждому пикселю, подтолкнуть пиксели, соответствующие ~ 10% значений RGB в массивах XY, усреднить массивы XY, вычислить / нормализовать вектор направления из массивов XY.

$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за исключением начала с изменения размера холста на 50% (допустимые потери) с использованием imagecreatefromjpeg('_test_cam_img.jpg');

3) 560 мс : то же, что и № 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) 340 мс : вызов системыДвоичный файл ImageMagick с помощью функции exec () передает ему местоположение изображения, клавишу цветности / цвета, изменение размера на 50%, 10% -ный параметр fuzz и модификатор sparse-color: для извлечения текстового (CSV-подобно) перечислить представление желаемых пикселей, затем использовать PHP для циклического перемещения по каждой строке, разбивать запятые и вставлять все пиксели в массивы XY, усреднять массивы XY, вычислять / нормализовать вектор направления из массивов XY. Я заметил, что вызов 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 создает текст «in»файл, содержащий путь к изображению и цветовой ключ / цвет, и начинает цикл до тех пор, пока не будет прочитан текстовый файл «out». Сценарий Python + OpenCV уже / всегда выполняет (останавливаемый) бесконечный цикл, постоянно ищущий текстовый файл «in» и, когда он существует, он читает его, анализирует значения, создает 1-битную маску, используя значения HSV ~ 10%(cv2.inRange) из файла «in», затем создает массив с использованием cv2.findNonZero (mask), вычисляет среднее значение массива и записывает его в текстовый файл «out», который PHP немедленно считывает, содержащий значение вектора направления. Это, безусловно, самый быстрый способ, который я нашел, но он неудобен, потому что он подразумевает, что скрипт 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) 38 мс : То же, что и № 5, за исключением того, что начинайте с изменения размера холста на 50% (приемлемая потеря), который, кажется, совсем не ускоряет процесс и даже кажется немного контрпродуктивным.

Есть ли более быстрый путь или это оптимально? Он будет работать каждую секунду на частоте 900 МГц, поэтому он должен быть быстрым. Я думаю, что 30 мс на процессоре с частотой 900 МГц будет около 150-200 мс (еще не проверено, ожидая отправки камеры)

1 Ответ

1 голос
/ 01 ноября 2019

Я быстро набрал php-vips :

#!/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.

Это сработает только при очень ограниченном освещении и настройках камеры, но, возможно, это нормально? Было бы легко сделать причудливое обнаружение шара, но, конечно, это немного замедлило бы его.

...