簡體   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