Самый быстрый способ вычислить и передать произвольный цветной центроид изображения в PHP

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

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

введите здесь описание изображения

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

введите здесь описание изображения

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

1) 2700 мс: используйте 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) 700 мс: то же, что и № 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% и параметр 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) 32 мс: PHP создает "входящий" текстовый файл, содержащий путь к изображению и ключ цветности/цвета, и начинает цикл до тех пор, пока не прочитает "исходящий" текстовый файл. Сценарий python + OpenCV уже/всегда запускает (останавливаемый) бесконечный цикл, постоянно ищущий «внутри» текстовый файл, и когда он существует, он читает его, взрывает значения, создает 1-битную маску, используя значения HSV ~ 10% (cv2.inRange) из «входящего» файла, затем создает массив с помощью cv2.findNonZero(mask), вычисляет среднее значение массива и записывает его в «исходящий» текстовый файл, который 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 МГц Rpi, поэтому оно должно быть быстрым. Я думаю, что 30 мс на процессоре 900 МГц будет около 150-200 мс (еще не тестировалось, жду отправки камеры)


person that-ben    schedule 31.10.2019    source источник
comment
30 мс - это 30 мс на всех процессорах. За исключением того, что они приближаются c.   -  person Klaus D.    schedule 31.10.2019
comment
Нет, это 30 мс на настольном процессоре AMD с тактовой частотой 4 ГГц. Я предполагаю, что он будет как минимум в 5-6 раз медленнее на процессоре Rpi 900 МГц. Может быть даже медленнее FAIK.   -  person that-ben    schedule 31.10.2019
comment
Довольно наивный расчет, учитывая, что у Raspberry Pi совсем другая архитектура.   -  person Klaus D.    schedule 31.10.2019
comment
пессимистично* ;-) Надеюсь, что ошибаюсь, и это быстрее, чем я ожидал...   -  person that-ben    schedule 31.10.2019
comment
Как вы думаете, последний Python + OpenCV для ARM будет использовать преимущества четырехъядерных процессоров Rpi или нет?   -  person that-ben    schedule 31.10.2019
comment
Я бы предложил переписать ваше решение Python и OpenCV на C++ и OpenCV. Еще более быстрым способом было бы написать модуль C для PHP, который интегрируется напрямую с чипом Rpi Broadcom и использует преимущества MMAL. функции декодера/конвейера.   -  person emcconville    schedule 01.11.2019
comment
@emcconville Спасибо за публикацию! Жестко, ваше утверждение кажется неверным, пожалуйста, прочитайте это: stackoverflow.com/questions/13432800/ Теперь компиляция модуля C для PHP действительно очень интересная идея, но я ищу что-то более простое В данный момент. Я не совсем инженер, а всего лишь простой любитель, делающий прототип в основном для себя в данный момент, поэтому я использую PHP вместо прямого подхода C, который, вероятно, был бы в миллион раз быстрее. PHP позволяет мне разрабатывать/тестировать в Windows, а затем развертывать НЕПОСРЕДСТВЕННО на моем Rpi.   -  person that-ben    schedule 02.11.2019
comment
@MarkSetchell Спасибо за ответ на мой вопрос! Действительно, этот вопрос может быть не полностью подробным, но я постараюсь добавить/объяснить все, что вам нужно, чтобы помочь мне решить эту проблему. Обратите внимание, я уверен, что вы очень компетентный человек. Не нужно чувствовать себя глупо или что-то в этом роде. В основном мой вопрос: каков самый высокопроизводительный способ написать процедуру, которая возвращает среднее значение всех пикселей данного цвета в данном изображении? Смотрите, в моем бывшем я ввожу фиолетовый как RGB, а розовая векторная стрелка представляет собой средние координаты фиолетового пятна. Дайте мне знать, если это все еще неясно.   -  person that-ben    schedule 02.11.2019
comment
@MarkSetchell v — это, например, вычисленный линейный вектор. Видите фиолетовое пятно на конце розовой стрелки? Все фиолетовые пиксели на всем изображении были усреднены, что дает среднее значение фиолетовой области, затем v представляет этот вектор. Вопрос не в том, как вычислить v, а в том, как получить v самым быстрым способом. В Q вы можете увидеть 6 подходов, которые я использовал, начиная с GD2 (2,7 секунды), затем Imagick для PHP, затем двоичный код Imagick через PHP exex(), затем OpenCV через Python. В любом случае процесс начинается с PHP и заканчивается на PHP. Все, что находится между ними, должно быть максимально быстрым.   -  person that-ben    schedule 02.11.2019
comment
@MarkSetchell О, и значения, которые вы видите напечатанными для v, просто потому, что я стандартизирую v между -1,0 (абсолютное нижнее/левое) до +1,0 (абсолютное нижнее/правое), поэтому ноль является средним (верхним, средним или нижним, не материя) картины.   -  person that-ben    schedule 02.11.2019
comment
Да да, PHP медленный, я знаю. Но для обслуживания веб-приложений и запуска всех видов системных вызовов, экспорта GPIO и т. д. он великолепен. Кроме того, насчет JPEG и PNG вы правы: P Я узнал об этом вчера после публикации PNG. Я по ошибке загрузил изображение в формате PNG, но уверяю вас, что на входе формат JPEG, а не PNG. Я действительно обнаружил, что обрабатывать JPEG намного быстрее, чем PNG.   -  person that-ben    schedule 02.11.2019
comment
@MarkSetchell Как вы можете видеть в Q, обработка изображения с помощью PHP (метод № 1) является НАИМЕНЕЕ эффективным методом из 6, так почему я в конечном итоге использую метод № 1? Python отлично читает камеру?   -  person that-ben    schedule 02.11.2019


Ответы (1)


Я быстро зашел в 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.

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

person jcupitt    schedule 01.11.2019
comment
Привет, спасибо за публикацию! В Linux я бы закончил с установкой этого вчера, но как, черт возьми, вы справитесь с этим в Windows? Я взял архив vips-dev-w64-web-8.8.3.zip и обнаружил внутри vips.exe вместе с кучей файлов lib*.dll, но не нашел файл php_vips.dll. Не могли бы вы добавить к своему ответу точную последовательность, чтобы получить это решение из ванильного WAMP (Apache 2.4, PHP 7.1) в 64-разрядной версии Windows 7? Это было бы очень признательно. Окончательное решение будет развернуто на Raspbian, но для тестирования мне нужно такое же решение на компьютере с Windows :( - person that-ben; 02.11.2019
comment
Извините, php-vips не работает в Windows. Я не думаю, что это было бы слишком сложно, но, насколько я знаю, никто этого не делал. - person jcupitt; 02.11.2019
comment
Лично я бы установил Ubuntu (очень близкую к raspbian) на виртуальную машину на вашем компьютере с Windows и разрабатывал там. Это будет ближе к вашей платформе развертывания, а собственные расширения php будут намного проще устанавливать. - person jcupitt; 02.11.2019
comment
Ой. Ну знаешь что? Давайте продолжим этот Q в течение пары недель, затем я получу свой кулачковый модуль Rpi и переделаю уровень непосредственно на Rpi в среде Linux... F Windows. Обязательно попробую ваш метод. Кроме того, Марк Сетчелл поднял хороший вопрос в связи с вашим решением: если бы я использовал PHP от А до Я, то как бы я эффективно считывал камеру с PHP на Rpi? Это вообще вещь? (никогда не удосужился проверить, потому что я предположил, что эти двое популярны, поэтому у них должна быть какая-то совместимость вместе, но теперь я сомневаюсь!) - person that-ben; 02.11.2019
comment
Камеры Linux подключаются с v4l2. Из php вам нужно открыть /dev/mycameradevice и выполнить серию вызовов read() и ioctl(). Это не сложно, но, конечно, это еще одна вещь, которую вам придется написать. Я не знаю, писал ли кто-нибудь php-модули, которые делают это для вас — php в основном используется на серверах, и у них, как правило, нет камер. - person jcupitt; 03.11.2019
comment
У меня не было бы проблем с этим, если бы производительность была адекватной, очевидно, что это не будет наравне с полным решением Python, я очень хорошо знаю об этом. Чтение /dev/ на самом деле то, что я делал в течение 2 лет для чтения/записи выводов GPIO, и хотя производительность не очень хорошая, она достаточно хороша для операций ~ 1 МГц, что соответствует моим потребностям на 100%. Сенсорам не нужен 1 миллион считываний в секунду. - person that-ben; 03.11.2019