Related
I need to convert a lot of x/y pixel coordinates from polygon segments (680x680) to the grid references (68x68) the polygon contains.
e.g grid references
1, 2, 3, 4
5, 6, 7, 8
9,10,11,12 etc
Performance is the ultimate goal. My working script does the job however with thousands of sets of polygon segments each minute I'm looking to improve speed further. Currently I'm using the GD library to draw a polygon, then with help of a bounding box, testing the brightness of each polygon pixel to get x/y coordinates, then finally converting those to a grid reference.
While the overhead of generating an image in memory isn't huge, there must be a better (or faster) way to do this.
Working example with output
$p = [];
$r = [];
$p['segments'] = [[144, 637], [225, 516], [85, 460], [30, 482]];
$r = segments_to_grid($p, $r);
print_r($r['grid']);
Array
(
[0] => 3133
[1] => 3134
[2] => 3135
[3] => 3136
[4] => 3137
[5] => 3138
[6] => 3199
[7] => 3200
...
...
[157] => 4092
[158] => 4093
[159] => 4094
[160] => 4095
[161] => 4161
[162] => 4162
[163] => 4229
)
Supporting functions
/**
* Convert a list of x/y coordinates to grid references
*
* #param array $p
* #param array $r
*
* #return array augmented $r
*/
function segments_to_grid($p, $r) {
$p['segments'] = isset($p['segments']) ? $p['segments'] : [];
// e.g, [[144,637],[225,516],[85,460],[30,482]]
// Return array
$r['grid'] = [];
// Define base dimensions
$w = 680;
$h = 680;
$poly_coords = [];
$min_x = $min_y = 680;
$max_x = $max_y = 0;
// Build an imagefilledpolygon compatible array and extract minimum and maximum for bounding box
foreach ($p['segments'] as $segment) {
$poly_coords[] = $segment[0];
$poly_coords[] = $segment[1];
$min_x = min($min_x, $segment[0]);
$min_y = min($min_y, $segment[1]);
$max_x = max($max_x, $segment[0]);
$max_y = max($max_y, $segment[1]);
}
// check we have something useful
if (!empty($poly_coords)) {
$r['code'] = 40;
// create image
$img = imagecreatetruecolor($w, $h);
// allocate colors (white background, black polygon)
$bg = imagecolorallocate($img, 255, 255, 255);
$black = imagecolorallocate($img, 0, 0, 0);
// fill the background
imagefilledrectangle($img, 0, 0, $w, $h, $bg);
// draw a polygon
if (imagefilledpolygon($img, $poly_coords, count($p['segments']), $black)) {
$r['code'] = 0;
// loop through the image and find the points that are black
for ($y = $min_y; $y < $max_y; $y = $y + 10) {
for ($x = $min_x; $x < $max_x; $x = $x + 10) {
$rgb = imagecolorat($img, $x, $y);
if (intval($rgb) < 16777215) {
$r['grid'][] = xy6802g68($x, $y);
}
}
}
} else {
$r['error'] = 'poly fail';
$r['code'] = 10;
}
imagedestroy($img);
} else {
$r['error'] = 'no coordinates';
$r['code'] = 20;
}
return ($r);
}
/**
* Converts X/Y 680x680 to 68x68 grid reference number.
*
* #param int $cX pixel x positon
* #param int $cY pixel y positon
* #return int grid reference number
*/
function xy6802g68($cX, $cY) {
$calcX = ceil($cX / 10) - 1;
$calcY = ceil($cY / 10) - 1;
$grid68 = $calcX + ($calcY * 68);
return ($grid68);
}
Solution thanks to #Oliver's comments.
Porting inpoly to PHP was 168 times faster than using GD image lib.
function segments_to_grid2($p, $r) {
// Define base dimensions
$vertx = $verty = [];
$min_x = $min_y = 680;
$max_x = $max_y = 0;
foreach ($p['segments'] as $segment) {
$vertx[] = $segment[0];
$verty[] = $segment[1];
$min_x = min($min_x, $segment[0]);
$min_y = min($min_y, $segment[1]);
$max_x = max($max_x, $segment[0]);
$max_y = max($max_y, $segment[1]);
}
if (!empty($vertx)) {
$nvert = count($vertx);
for ($y = $min_y; $y < $max_y; $y = $y + 10) {
for ($x = $min_x; $x < $max_x; $x = $x + 10) {
if (inpoly($nvert, $vertx, $verty, $x, $y)) {
$r['grid'][] = xy6802g68($x, $y);
}
}
}
}
return $r;
}
function inpoly($nvert, $vertx, $verty, $testx, $testy) {
$i = $j = $c = 0;
for ($i = 0, $j = $nvert - 1; $i < $nvert; $j = $i++) {
if ((($verty[$i] > $testy) != ($verty[$j] > $testy)) && ($testx < ($vertx[$j] - $vertx[$i]) * ($testy - $verty[$i]) / ($verty[$j] - $verty[$i]) + $vertx[$i])) {
$c = !$c;
}
}
return $c;
}
Running this 100 times
GD library version:
[segments_to_grid] => 0.0027089119 seconds
inpoly version
[segments_to_grid2] => 0.0001449585 seconds
168 times faster and equivalent output
Thanks Oliver!
Testing if a point lies inside a polygon is a well-known problem.
The classical solution is an algorithm called "ray casting":
Start from the point
Cast a ray in some arbitrary direction and count the number of times it crosses a polygon segment
The parity of the number gives the result (odd: inside; even: outside)
A C implementation of the algorithm is given here:
int pnpoly(int nvert, float *vertx, float *verty, float testx, float testy)
{
int i, j, c = 0;
for (i = 0, j = nvert-1; i < nvert; j = i++) {
if ( ((verty[i]>testy) != (verty[j]>testy)) &&
(testx < (vertx[j]-vertx[i]) * (testy-verty[i]) / (verty[j]-verty[i]) + vertx[i]) )
c = !c;
}
return c;
}
A possible PHP version is:
function pnpoly($nvert, $vertx, $verty, $testx, $testy) {
$c = false;
for ($i = 0, $j = $nvert - 1; $i < $nvert; $j = $i++) {
if ((($verty[$i] > $testy) != ($verty[$j] > $testy))
&& ($testx < ($vertx[$j] - $vertx[$i]) * ($testy - $verty[$i]) / ($verty[$j] - $verty[$i]) + $vertx[$i])) {
$c = !$c;
}
}
return $c;
}
I have a function that, given an image with a transparent background and an unknown object in it, finds the top, left, right and bottom boundaries of the object. The purpose is so that I can simply draw a box around the boundaries of the object. I'm not trying to detect the actual edges of the object - just the top most, bottom most, etc.
My function works well, but is slow because it scans every single pixel in the image.
My question is: Is there a faster, more efficient way to detected the upper-most, left-most, right-most, and bottom-most non-transparent pixel in an image, using stock PHP/GD functionality?
There's a catch that affects the options: the object in the image may have transparent parts. For example, if it's an image of a non-filled shape.
public static function getObjectBoundaries($image)
{
// this code looks for the first non white/transparent pixel
// from the top, left, right and bottom
$imageInfo = array();
$imageInfo['width'] = imagesx($image);
$imageInfo['height'] = imagesy($image);
$imageInfo['topBoundary'] = $imageInfo['height'];
$imageInfo['bottomBoundary'] = 0;
$imageInfo['leftBoundary'] = $imageInfo['width'];
$imageInfo['rightBoundary'] = 0;
for ($x = 0; $x <= $imageInfo['width'] - 1; $x++) {
for ($y = 0; $y <= $imageInfo['height'] - 1; $y++) {
$pixelColor = imagecolorat($image, $x, $y);
if ($pixelColor != 2130706432) { // if not white/transparent
$imageInfo['topBoundary'] = min($y, $imageInfo['topBoundary']);
$imageInfo['bottomBoundary'] = max($y, $imageInfo['bottomBoundary']);
$imageInfo['leftBoundary'] = min($x, $imageInfo['leftBoundary']);
$imageInfo['rightBoundary'] = max($x, $imageInfo['rightBoundary']);
}
}
}
return $imageInfo;
}
Function calls in PHP are expensive. Calling imagecolorat() per pixel will absolutely ruin performance. Efficient coding in PHP means finding a built-in function that can somehow do the job. The following code makes use of the palette GD functions. At a glance it might not be intuitive but the logic is actually pretty simple: the code keeps copying the image a line of pixels at a time until it notices that it requires more than one colors to represent them.
function getObjectBoundaries2($image) {
$width = imagesx($image);
$height = imagesy($image);
// create a one-pixel high image that uses a PALETTE
$line = imagecreate($width, 1);
for($y = 0; $y < $height; $y++) {
// copy a row of pixels into $line
imagecopy($line, $image, 0, 0, 0, $y, $width, 1);
// count the number of colors in $line
// if it's one, then assume it's the transparent color
$count = imagecolorstotal($line);
if($count > 1) {
// okay, $line has employed more than one color so something's there
// look at the first color in the palette to ensure that our initial
// assumption was correct
$firstColor = imagecolorsforindex($line, 0);
if($firstColor['alpha'] == 127) {
$top = $y;
} else {
// it was not--the first color encountered was opaque
$top = 0;
}
break;
}
}
if(!isset($top)) {
// image is completely empty
return array('width' => $width, 'height' => $height);
}
// do the same thing from the bottom
$line = imagecreate($width, 1);
for($y = $height - 1; $y > $top; $y--) {
imagecopy($line, $image, 0, 0, 0, $y, $width, 1);
$count = imagecolorstotal($line);
if($count > 1) {
$firstColor = imagecolorsforindex($line, 0);
if($firstColor['alpha'] == 127) {
$bottom = $y;
} else {
$bottom = $height - 1;
}
break;
}
}
$nonTransparentHeight = $bottom - $top + 1;
// scan from the left, ignoring top and bottom parts known to be transparent
$line = imagecreate(1, $nonTransparentHeight);
for($x = 0; $x < $width; $x++) {
imagecopy($line, $image, 0, 0, $x, $top, 1, $nonTransparentHeight);
$count = imagecolorstotal($line);
if($count > 1) {
$firstColor = imagecolorsforindex($line, 0);
if($firstColor['alpha'] == 127) {
$left = $x;
} else {
$left = 0;
}
break;
}
}
// scan from the right
$line = imagecreate(1, $nonTransparentHeight);
for($x = $width - 1; $x > $left; $x--) {
imagecopy($line, $image, 0, 0, $x, $top, 1, $nonTransparentHeight);
$count = imagecolorstotal($line);
if($count > 1) {
$firstColor = imagecolorsforindex($line, 0);
if($firstColor['alpha'] == 127) {
$right = $x;
} else {
$right = $width - 1;
}
break;
}
}
return array('width' => $width, 'height' => $height, 'topBoundary' => $top, 'bottomBoundary' => $bottom, 'leftBoundary' => $left, 'rightBoundary' => $right);
}
I think you could test the 4 sides one after an other, stopping as soon as a pixel is found.
For the top boundary (untested code) :
// false so we can test it's value
$bound_top = false;
// The 2 loops have 2 end conditions, if end of row/line, or pixel found
// Loop from top to bottom
for ($y = 0; $y < $img_height && $bound_top === false; $y++) {
// Loop from left to right (right to left would work to)
for ($x = 0; $x < $img_width && $bound_top === false; $x++) {
if (imageColorAt($img, $x, $y) != 2130706432) {
$bound_top = $y;
}
}
}
After the loops, if $bound_top is still false, don't bother checking the other sides, you checked all pixels, the image is empty. If not, just do the same for the other sides.
Not every pixel needs to be examined. The following code checks columns from left to right to get leftBoundary, right to left to get rightBoundary, rows from top to bottom (while excluding pixels we've already checked) to get topBoundary, and similarly for bottomBoundary.
function get_boundary($image)
{
$imageInfo = array();
$imageInfo['width'] = imagesx($image);
$imageInfo['height'] = imagesy($image);
for ($x = 0; $x < $imageInfo['width']; $x++) {
if (!is_box_empty($image, $x, 0, 1, $imageInfo['height'])) {
$imageInfo['leftBoundary'] = $x;
break;
}
}
for ($x = $imageInfo['width']-1; $x >= 0; $x--) {
if (!is_box_empty($image, $x, 0, 1, $imageInfo['height'])) {
$imageInfo['rightBoundary'] = $x;
break;
}
}
for ($y = 0; $y < $imageInfo['height']; $y++) {
if (!is_box_empty($image, $imageInfo['leftBoundary'], $y, $imageInfo['rightBoundary']-$imageInfo['leftBoundary']+1, 1)) {
$imageInfo['topBoundary'] = $y;
break;
}
}
for ($y = $imageInfo['height']-1; $y >= 0; $y--) {
if (!is_box_empty($image, $imageInfo['leftBoundary'], $y, $imageInfo['rightBoundary']-$imageInfo['leftBoundary']+1, 1)) {
$imageInfo['bottomBoundary'] = $y;
break;
}
}
return $imageInfo;
}
function is_box_empty($image, $x, $y, $w, $h)
{
for ($i = $x; $i < $x+$w; $i++) {
for ($j = $y; $j < $y+$h; $j++) {
$pixelColor = imagecolorat($image, $i, $j);
if ($pixelColor != 2130706432) { // if not white/transparent
return false;
}
}
}
return true;
}
Trying to convert the following PHP function to Python but getting the following error. What would be the working Python equivalent to the below PHP function?
line 140, in doDetectBigToSmall
for scale in xrange(start_scale, scale > 1,scale = scale* scale_update):
UnboundLocalError: local variable 'scale' referenced before assignment
PHP CODE:
protected function doDetectBigToSmall($ii, $ii2, $width, $height)
{
$s_w = $width/20.0;
$s_h = $height/20.0;
$start_scale = $s_h < $s_w ? $s_h : $s_w;
$scale_update = 1 / 1.2;
for ($scale = $start_scale; $scale > 1; $scale *= $scale_update) {
$w = (20*$scale) >> 0;
$endx = $width - $w - 1;
$endy = $height - $w - 1;
$step = max($scale, 2) >> 0;
$inv_area = 1 / ($w*$w);
for ($y = 0; $y < $endy; $y += $step) {
for ($x = 0; $x < $endx; $x += $step) {
$passed = $this->detectOnSubImage($x, $y, $scale, $ii, $ii2, $w, $width+1, $inv_area);
if ($passed) {
return array('x'=>$x, 'y'=>$y, 'w'=>$w);
}
} // end x
} // end y
} // end scale
return null;
}
PYTHON CODE:
def doDetectBigToSmall(self,ii, ii2, width, height):
s_w = width/20.0
s_h = height/20.0
start_scale = s_h if s_h < s_w else s_w
scale_update = 1 / 1.2
for scale in xrange(start_scale, scale > 1,scale = scale* scale_update):
w = (20*scale) >> 0
endx = width - w - 1
endy = height - w - 1
step = max(scale, 2) >> 0
inv_area = 1 / (w*w)
for y in xrange(0,y < endy,y = y + step):
for x in xrange(0, x < endx, x= x + step):
passed = self.detectOnSubImage(x, y, scale, ii, ii2, w, width+1, inv_area)
if (passed):
return {'x': x, 'y': y, 'w': w}
You have no idea what xrange() does ;-) So read the docs before you try it again. In the meantime, replace:
for scale in xrange(start_scale, scale > 1,scale = scale* scale_update):
with
scale = start_scale
while scale > 1:
and, at the end of the loop, add:
scale *= scale_update
All your other uses of xrange() are similarly broken, but you have to make some effort to learn what it does.
This works for me:
def strpos_r(haystack, needle):
positions = []
position = haystack.rfind(needle)
while position != -1:
positions.append(position)
haystack = haystack[:position]
position = haystack.rfind(needle)
return positions
Also, functions should not really handle input errors for you. You usually just return False or let the function throw an execution error.
This is happening because xrange is a function and you are passing an uninitialized value to it. Scale will not be initialized until after xrange is run and returns a value. Python generally used for loops to iterate over lists. I recommend rewriting your code using while loops.
What I'd like here is a working, optimized version of my current code. While my function does return an array with actual results, I don't know if they are correct (I'm not a mathematics guru and I don't know Java code to compare my results against known implementations). Secondly, I'd like the function to be able to accept custom table sizes, but I don't know how to do that. Is table size equivalent to resampling the image? Am I applying the coefficients correctly?
// a lot of processing is required for large images
$image = imagecreatetruecolor(21, 21);
$black = imagecolorallocate($image, 0, 0, 0);
$white = imagecolorallocate($image, 255, 255, 255);
imagefilledellipse($image, 10, 10, 15, 15, $white);
print_r(imgDTC($image));
function imgDTC($img, $tableSize){
// m1 = Matrix1, an associative array with pixel data from the image
// m2 = Matrix2, an associative array with DCT Frequencies
// x1, y1 = coordinates in matrix1
// x2, y2 = coordinates in matrix2
$m1 = array();
$m2 = array();
// iw = image width
// ih = image height
$iw = imagesx($img);
$ih = imagesy($img);
// populate matrix1
for ($x1=0; $x1<$iw; $x1++) {
for ($y1=0; $y1<$ih; $y1++) {
$m1[$x1][$y1] = imagecolorat($img, $x1, $y1) & 0xff;
}
}
// populate matrix2
// for each coordinate in matrix2
for ($x2=0;$x2<$iw;$x2++) {
for ($y2=0;$y2<$ih;$y2++) {
// for each coordinate in matrix1
$sum = 1;
for ($x1=0;$x1<$iw;$x1++) {
for ($y1=0;$y1<$ih;$y1++) {
$sum +=
cos(((2*$x1+1)/(2*$iw))*$x2*pi()) *
cos(((2*$y1+1)/(2*$ih))*$y2*pi()) *
$m1[$x1][$y1]
;
}
}
// apply coefficients
$sum *= .25;
if ($x2 == 0 || $y2 == 0) {
$sum *= 1/sqrt(2);
}
$m2[$x2][$y2] = $sum;
}
}
return $m2;
}
My PHP function is a derivitive from this post in Java: Problems with DCT and IDCT algorithm in java. I have rewritten the code for php and readability. Ultimately, I am working on a script which will enable me to compare images and find similarities. The technique is outlined here: http://www.hackerfactor.com/blog/index.php?/archives/432-Looks-Like-It.html.
Thanks!
This is how I performed my DCT what I'm doing here is to perform a 1 dimension DCT on each row. Then I took the result an perform the DTC on each column it's faster.
function dct1D($in) {
$results = array();
$N = count($in);
for ($k = 0; $k < $N; $k++) {
$sum = 0;
for ($n = 0; $n < $N; $n++) {
$sum += $in[$n] * cos($k * pi() * ($n + 0.5) / ($N));
}
$sum *= sqrt(2 / $N);
if ($k == 0) {
$sum *= 1 / sqrt(2);
}
$results[$k] = $sum;
}
return $results;
}
function optimizedImgDTC($img) {
$results = array();
$N1 = imagesx($img);
$N2 = imagesy($img);
$rows = array();
$row = array();
for ($j = 0; $j < $N2; $j++) {
for ($i = 0; $i < $N1; $i++)
$row[$i] = imagecolorat($img, $i, $j);
$rows[$j] = dct1D($row);
}
for ($i = 0; $i < $N1; $i++) {
for ($j = 0; $j < $N2; $j++)
$col[$j] = $rows[$j][$i];
$results[$i] = dct1D($col);
}
return $results;
}
Most algorithm I found on internet assume that the input matrix is 8x8. That's why you multiplyed by 0.25.
In general you should multiply by sqrt(2 / N) a 1D matrix and here we are in 2D so sqrt(2/N1) * sqrt(2/N2). If you do this for N1 = 8 and N2 = 8:
sqrt(2/8)^2 = 2/8 = 1/4 = 0.25
The other thing was to multiply by 1/sqrt(2) X0 it's for 1D matrix here we are in 2D so you multiply when k1 = 0 or k2 = 0. When k1 = 0 and k2 = 0 you have to do it twice.
First you need to test your function so find any working implementation. And compare results from your implementation with the results of the working implementation (with the same input).
If you whant your code to be faster you can look at this paper http://infoscience.epfl.ch/record/34246/files/Vetterli85.pdf (the first 2 parts).
In your case you can't use a custom table size because it should match the image size (can be wrong).
For a while now I've been interested in fractals, the math behind them and the visuals they can produce.
I just can't really figure out how to map the mathematical formula to a piece of code that draws the picture.
Given this formula for the mandelbrot set: Pc(z) = z * z + c
How does that compare to the following code:
$outer_adder = ($MaxIm - $MinIm) / $Lines;
$inner_adder = ($MaxRe - $MinRe) / $Cols;
for($Im = $MinIm; $Im <= $MaxIm; $Im += $outer_adder)
{
$x=0;
for($Re = $MinRe; $Re <= $MaxRe; $Re += $inner_adder)
{
$zr = $Re;
$zi = $Im;
for($n = 0; $n < $MaxIter; ++$n)
{
$a = $zr * $zr;
$b = $zi * $zi;
if($a + $b > 2) break;
$zi = 2 * $zr * $zi + $Im;
$zr = $a - $b + $Re;
}
$n = ($n >= $MaxIter ? $MaxIter - 1 : $n);
ImageFilledRectangle($img, $x, $y, $x, $y, $c[$n]);
++$x;
}
++$y;
}
Code is not complete, just showing the main iteration part for brevity.
So the question is: could someone explain to me how the math compares to the code?
Edit: To be clear, I've found dozens of resources explaining the math, and dozens of resources showing code, but nowhere can I find a good explanation of the two combined.
Disclaimer. I didn't know anything about fractals before, but always wanted to know, so I've read the wikipedia article and decided to write what I've found here.
As they say, if you want to understand something, try explaining it to someone else. ;)
Okay, we're going to operate on complex numbers. A complex number is actually a pair of (real) numbers, so, for us php programmers, let it be a two-elements array.
/// Construct a complex number from two reals
function cpl($re, $im) {
return array($re, $im);
}
Now we need to tell php how to do arithmetics on our complex numbers. We'll need addition, multiplication and the mod ("norm") operator. (see http://mathworld.wolfram.com/topics/ComplexNumbers.html for more details).
/// Add two complex numbers.
function cadd($p, $q) {
return cpl(
$p[0] + $q[0],
$p[1] + $q[1]);
}
/// Multiply two complex numbers.
function cmul($p, $q) {
return cpl(
$p[0] * $q[0] - $p[1] * $q[1],
$p[0] * $q[1] + $p[1] * $q[0]);
}
/// Return the norm of the complex number.
function cmod($p) {
return sqrt($p[0] * $p[0] + $p[1] * $p[1]);
}
Now we write a function that returns true if the given (complex) point $c belongs to the mandelbrot set
A point c belongs to the set if all points z = z^2 + c lie inside the circle with the radius 2.
We start with the complex number z = (0, 0).
On each step we calculate z = z * z + c.
If modulus of z > 2 - that is, we're out of the circle - the point is NOT in set
Otherwise repeat the step.
To prevent that from looping endlessly, limit the max number of iterations.
function is_in_mandelbrot_set($c, $iterations) {
$z = cpl(0, 0);
do {
if(cmod($z) >= 2)
return false;
$z = cadd(cmul($z, $z), $c);
} while($iterations--);
return true;
}
The rest has nothing to do with math and is quite obvious
function mandelbrot($img, $w, $h) {
$color = imagecolorallocate($img, 0xFF, 0, 0);
$zoom = 50;
$iters = 30;
for($x = 0; $x < $w; $x++) {
for($y = 0; $y < $h; $y++) {
// scale our integer points
// to be small real numbers around 0
$px = ($x - $w / 2) / $zoom;
$py = ($y - $h / 2) / $zoom;
$c = cpl($px, $py);
if(is_in_mandelbrot_set($c, $iters))
imagesetpixel($img, $x, $y, $color);
}
}
return $img;
}
$w = 200;
$h = 200;
header("Content-type: image/png");
imagepng(
mandelbrot(
imagecreatetruecolor($w, $h), $w, $h));
Result
Of course, this code is ineffective to the extreme. Its only purpose is to understand the math concept.