I'm currently trying to get all pixel data inside of an image, and return it in JSON after encoding an Array. However, when I try to insert the $y data into the array, it always inserts 144. No in-between, always 144. When I echo $y, however, I get "0, 1, 2, etc."
$x, $r, $g, and $b are correct.
Any ideas? Here's my code:
<?php
header('Content-Type: application/json');
class PixelData {
private $ar = array(
"pixeldata" => [
]
);
public function getPixel($x, $y, $im) {
echo $y; // echoes "0, 1, 2, 3, etc."
global $ar;
$ar["pixeldata"][$x]["x"] = $x;
$ar["pixeldata"][$x]["y"] = $y;
$rgb = imagecolorat($im, $x, $y);
$r = ($rgb >> 16) & 0xFF;
$g = ($rgb >> 8) & 0xFF;
$b = $rgb & 0xFF;
$ar["pixeldata"][$x]["r"] = $r;
$ar["pixeldata"][$x]["g"] = $g;
$ar["pixeldata"][$x]["b"] = $b;
}
}
$src = "D:\Pictures\Test.png";
$im = imagecreatefrompng($src);
$size = getimagesize($src);
$width = imagesx($im);
$height = imagesy($im);
for($x = 0; $x < $width; $x++)
{
for($y = 0; $y < $height; $y++)
{
$pd = new PixelData();
$pd->getPixel($x, $y, $im);
}
}
$js = json_encode($ar);
echo $js;
?>
To elaborate further on my comment:
Your array is basically one-dimensional, because the only variable aspect is $ar['pixeldata'][$x]; you're never adding $y as another dimension. So, every time you increment $y in the inner-most for loop to go to the next level of $height you're overwriting the previous $x values. Basically, by the time your script is done, pixeldata will only contain the data of the top-most row of pixels, therefore they're always 144.
I also didn't notice until now, but if you wanted to store the data within the PixelData private array, you can't create a new instance within the for loops, you'd need to do that outside of the for loops.
This is probably what you want. As you can see, I've added an extra dimension to you pixeldata array, so as to include both $x and $y dimensions.
<?php
header('Content-Type: application/json');
class PixelData {
private $ar = array(
"pixeldata" => [
]
);
public function getPixel($x, $y, $im) {
echo $y; // echoes "0, 1, 2, 3, etc."
// Instantiate an array for this pixel
$this->ar["pixeldata"][$x][$y] = [];
$this->ar["pixeldata"][$x][$y]["x"] = $x;
$this->ar["pixeldata"][$x][$y]["y"] = $y;
$rgb = imagecolorat($im, $x, $y);
$r = ($rgb >> 16) & 0xFF;
$g = ($rgb >> 8) & 0xFF;
$b = $rgb & 0xFF;
$this->ar["pixeldata"][$x][$y]["r"] = $r;
$this->ar["pixeldata"][$x][$y]["g"] = $g;
$this->ar["pixeldata"][$x][$y]["b"] = $b;
}
public function getAr() {
return $this->ar;
}
}
$src = "D:\Pictures\Test.png";
$im = imagecreatefrompng($src);
$size = getimagesize($src);
$width = imagesx($im);
$height = imagesy($im);
$pd = new PixelData();
for($x = 0; $x < $width; $x++)
{
for($y = 0; $y < $height; $y++)
{
$pd->getPixel($x, $y, $im);
}
}
$js = json_encode($pd->getAr());
echo $js;
?>
I have two images(small and big). Big one contains a small one. Like if the small one is a photo and a big one is a page from the photo album.
How do I get coordinates of that small image in the big one using PHP? And also I need to know the size of that image in big one...so just a(x,y) coordinate of any angle and sizes of sides of that presentation of the small image...
(x,y, width, height)
I've already asked the question like that and got a brilliant answer (here) but I've forgot to mention over there that the size of a small image could be different from the the size of that image in the big image...
And also if it is possible to deal with a presentation of that small image in the big image can have something covering one of its angles... Like in this example:
Small image:
Big image:
Small image always has just a rectangular shape.
Alright, this answer does not perfectly answer the question, but it should give you a good start! I know I repeat myself in the code, but my goal was simply to get something working so you can build on it, this isn't production code!
Preconditions
Starting with the large picture:
We need to find as best as possible the position of this other picture:
I decided to break the process into many substeps, which you could improve or remove depending on what you want the code to do.
For testing purposes, I did test my algorithm on different input images so you'll see a variable defining what file to load...
We start with:
function microtime_float()
{
list($usec, $sec) = explode(" ", microtime());
return ((float)$usec + (float)$sec);
}
$time_start = microtime_float();
$largeFilename = "large.jpg";
$small = imagecreatefromjpeg("small.jpg");
$large = imagecreatefromjpeg($largeFilename);
and
imagedestroy($small);
imagedestroy($large);
$time_end = microtime_float();
echo "in " . ($time_end - $time_start) . " seconds\n";
To have a good idea on our performances. Luckily, most of the algorithm was pretty fast so I didn't have to optimize more.
Background Detection
I started by detecting the background color. I assumed that the background color would be the color most present in the picture. To do this, I only counted how many references of each color I could find in the large picture, sort it with decending values and took the first one as the background color (should allow the code to be adaptable if you changed the source pictures)
function FindBackgroundColor($image)
{
// assume that the color that's present the most is the background color
$colorRefcount = array();
$width = imagesx($image);
$height = imagesy($image);
for($x = 0; $x < $width; ++$x)
{
for($y = 0; $y < $height; ++$y)
{
$color = imagecolorat($image, $x, $y);
if(isset($colorRefcount[$color]))
$colorRefcount[$color] = $colorRefcount[$color] + 1;
else
$colorRefcount[$color] = 1;
}
}
arsort($colorRefcount);
reset($colorRefcount);
return key($colorRefcount);
}
$background = FindBackgroundColor($large); // Should be white
Partitionning
My first step was to try to find all the regions where non background pixels were. With a little padding, I was able to group regions into bigger regions (so that a paragraph would be a single region instead of multiple individual letters). I started with a padding of 5 and got good enough results so I stuck with it.
This is broken into multiple function calls, so here we go:
function FindRegions($image, $backgroundColor, $padding)
{
// Find all regions within image where colors are != backgroundColor, including a padding so that adjacent regions are merged together
$width = imagesx($image);
$height = imagesy($image);
$regions = array();
for($x = 0; $x < $width; ++$x)
{
for($y = 0; $y < $height; ++$y)
{
$color = imagecolorat($image, $x, $y);
if($color == $backgroundColor)
{
continue;
}
if(IsInsideRegions($regions, $x, $y))
{
continue;
}
$region = ExpandRegionFrom($image, $x, $y, $backgroundColor, $padding);
array_push($regions, $region);
}
}
return $regions;
}
$regions = FindRegions($large, $background, 5);
Here, we iterate on every pixel of the picture, if its background color, we discard it, otherwise, we check if its position is already present in a region we found, if that's the case, we skip it too. Now, if we didn't skip the pixel, it means that it's a colored pixel that should be part of a region, so we start ExpandRegionFrom this pixel.
The code to check if we're inside a region is pretty simple:
function IsInsideRegions($regions, $x, $y)
{
foreach($regions as $region)
{
if(($region["left"] <= $x && $region["right"] >= $x) &&
($region["bottom"] <= $y && $region["top"] >= $y))
{
return true;
}
}
return false;
}
Now, the expanding code will try to grow the region in each direction and will do so as long as it found new pixels to add to the region:
function ExpandRegionFrom($image, $x, $y, $backgroundColor, $padding)
{
$width = imagesx($image);
$height = imagesy($image);
$left = $x;
$bottom = $y;
$right = $x + 1;
$top = $y + 1;
$expanded = false;
do
{
$expanded = false;
$newLeft = ShouldExpandLeft($image, $backgroundColor, $left, $bottom, $top, $padding);
if($newLeft != $left)
{
$left = $newLeft;
$expanded = true;
}
$newRight = ShouldExpandRight($image, $backgroundColor, $right, $bottom, $top, $width, $padding);
if($newRight != $right)
{
$right = $newRight;
$expanded = true;
}
$newTop = ShouldExpandTop($image, $backgroundColor, $top, $left, $right, $height, $padding);
if($newTop != $top)
{
$top = $newTop;
$expanded = true;
}
$newBottom = ShouldExpandBottom($image, $backgroundColor, $bottom, $left, $right, $padding);
if($newBottom != $bottom)
{
$bottom = $newBottom;
$expanded = true;
}
}
while($expanded == true);
$region = array();
$region["left"] = $left;
$region["bottom"] = $bottom;
$region["right"] = $right;
$region["top"] = $top;
return $region;
}
The ShouldExpand methods could have been written in a cleaner fashion, but I went for something fast to prototype with:
function ShouldExpandLeft($image, $background, $left, $bottom, $top, $padding)
{
// Find the farthest pixel that is not $background starting at $left - $padding closing in to $left
for($x = max(0, $left - $padding); $x < $left; ++$x)
{
for($y = $bottom; $y <= $top; ++$y)
{
$pixelColor = imagecolorat($image, $x, $y);
if($pixelColor != $background)
{
return $x;
}
}
}
return $left;
}
function ShouldExpandRight($image, $background, $right, $bottom, $top, $width, $padding)
{
// Find the farthest pixel that is not $background starting at $right + $padding closing in to $right
$from = min($width - 1, $right + $padding);
$to = $right;
for($x = $from; $x > $to; --$x)
{
for($y = $bottom; $y <= $top; ++$y)
{
$pixelColor = imagecolorat($image, $x, $y);
if($pixelColor != $background)
{
return $x;
}
}
}
return $right;
}
function ShouldExpandTop($image, $background, $top, $left, $right, $height, $padding)
{
// Find the farthest pixel that is not $background starting at $top + $padding closing in to $top
for($x = $left; $x <= $right; ++$x)
{
for($y = min($height - 1, $top + $padding); $y > $top; --$y)
{
$pixelColor = imagecolorat($image, $x, $y);
if($pixelColor != $background)
{
return $y;
}
}
}
return $top;
}
function ShouldExpandBottom($image, $background, $bottom, $left, $right, $padding)
{
// Find the farthest pixel that is not $background starting at $bottom - $padding closing in to $bottom
for($x = $left; $x <= $right; ++$x)
{
for($y = max(0, $bottom - $padding); $y < $bottom; ++$y)
{
$pixelColor = imagecolorat($image, $x, $y);
if($pixelColor != $background)
{
return $y;
}
}
}
return $bottom;
}
Now, to see if the algorithm was succesful, I added some debug code.
Debug Rendering
I created a second image to store debug info and store it on disk so I could later see my progress.
Using the following code:
$large2 = imagecreatefromjpeg($largeFilename);
$red = imagecolorallocate($large2, 255, 0, 0);
$green = imagecolorallocate($large2, 0, 255, 0);
$blue = imagecolorallocate($large2, 0, 0, 255);
function DrawRegions($image, $regions, $color)
{
foreach($regions as $region)
{
imagerectangle($image, $region["left"], $region["bottom"], $region["right"], $region["top"], $color);
}
}
DrawRegions($large2, $regions, $red);
imagejpeg($large2, "regions.jpg");
I could validate that my partitioning code was doing a decent job:
Aspect Ratio
I decided to filter out some regions based on aspect ratio (the ratio between the width and the height). Other filtering could be applied such as average pixel color or something, but the aspect ratio check was very fast so I used it.
I simply defined a "window" where regions would be kept, if their aspect ration was between a minimum and maximum value;
$smallAspectRatio = imagesx($small) / imagesy($small);
function PruneOutWrongAspectRatio($regions, $minAspectRatio, $maxAspectRatio)
{
$result = array();
foreach($regions as $region)
{
$aspectRatio = ($region["right"] - $region["left"]) / ($region["top"] - $region["bottom"]);
if($aspectRatio >= $minAspectRatio && $aspectRatio <= $maxAspectRatio)
{
array_push($result, $region);
}
}
return $result;
}
$filterOnAspectRatio = true;
if($filterOnAspectRatio == true)
{
$regions = PruneOutWrongAspectRatio($regions, $smallAspectRatio - 0.1 * $smallAspectRatio, $smallAspectRatio + 0.1 * $smallAspectRatio);
DrawRegions($large2, $regions, $blue);
}
imagejpeg($large2, "aspectratio.jpg");
By adding the DrawRegions call, I now paint in blue the regions that are still in the list as potential positions:
As you can see, only 4 position remains!
Finding the Corners
We're almost done! Now, what I'm doing is looking at the colors in the four corners from the small picture, and try to find the best matching pixel in the corners of the remaining regions. This code has the most potential to fail so if you have to invest time in improving the solution, this code would be a good candidate.
function FindCorners($large, $small, $regions)
{
$result = array();
$bottomLeftColor = imagecolorat($small, 0, 0);
$blColors = GetColorComponents($bottomLeftColor);
$bottomRightColor = imagecolorat($small, imagesx($small) - 1, 0);
$brColors = GetColorComponents($bottomRightColor);
$topLeftColor = imagecolorat($small, 0, imagesy($small) - 1);
$tlColors = GetColorComponents($topLeftColor);
$topRightColor = imagecolorat($small, imagesx($small) - 1, imagesy($small) - 1);
$trColors = GetColorComponents($topRightColor);
foreach($regions as $region)
{
$bottomLeft = null;
$bottomRight = null;
$topLeft = null;
$topRight = null;
$regionWidth = $region["right"] - $region["left"];
$regionHeight = $region["top"] - $region["bottom"];
$maxRadius = min($regionWidth, $regionHeight);
$topLeft = RadialFindColor($large, $tlColors, $region["left"], $region["top"], 1, -1, $maxRadius);
$topRight = RadialFindColor($large, $trColors, $region["right"], $region["top"], -1, -1, $maxRadius);
$bottomLeft = RadialFindColor($large, $blColors, $region["left"], $region["bottom"], 1, 1, $maxRadius);
$bottomRight = RadialFindColor($large, $brColors, $region["right"], $region["bottom"], -1, 1, $maxRadius);
if($bottomLeft["found"] && $topRight["found"] && $topLeft["found"] && $bottomRight["found"])
{
$left = min($bottomLeft["x"], $topLeft["x"]);
$right = max($bottomRight["x"], $topRight["x"]);
$bottom = min($bottomLeft["y"], $bottomRight["y"]);
$top = max($topLeft["y"], $topRight["y"]);
array_push($result, array("left" => $left, "right" => $right, "bottom" => $bottom, "top" => $top));
}
}
return $result;
}
$closeOnCorners = true;
if($closeOnCorners == true)
{
$regions = FindCorners($large, $small, $regions);
DrawRegions($large2, $regions, $green);
}
I tried to find the matching color by increasing "radially" (its basically squares) from the corners until I find a matching pixel (within a tolerance):
function GetColorComponents($color)
{
return array("red" => $color & 0xFF, "green" => ($color >> 8) & 0xFF, "blue" => ($color >> 16) & 0xFF);
}
function GetDistance($color, $r, $g, $b)
{
$colors = GetColorComponents($color);
return (abs($r - $colors["red"]) + abs($g - $colors["green"]) + abs($b - $colors["blue"]));
}
function RadialFindColor($large, $color, $startx, $starty, $xIncrement, $yIncrement, $maxRadius)
{
$result = array("x" => -1, "y" => -1, "found" => false);
$treshold = 40;
for($r = 1; $r <= $maxRadius; ++$r)
{
$closest = array("x" => -1, "y" => -1, "distance" => 1000);
for($i = 0; $i <= $r; ++$i)
{
$x = $startx + $i * $xIncrement;
$y = $starty + $r * $yIncrement;
$pixelColor = imagecolorat($large, $x, $y);
$distance = GetDistance($pixelColor, $color["red"], $color["green"], $color["blue"]);
if($distance < $treshold && $distance < $closest["distance"])
{
$closest["x"] = $x;
$closest["y"] = $y;
$closest["distance"] = $distance;
break;
}
}
for($i = 0; $i < $r; ++$i)
{
$x = $startx + $r * $xIncrement;
$y = $starty + $i * $yIncrement;
$pixelColor = imagecolorat($large, $x, $y);
$distance = GetDistance($pixelColor, $color["red"], $color["green"], $color["blue"]);
if($distance < $treshold && $distance < $closest["distance"])
{
$closest["x"] = $x;
$closest["y"] = $y;
$closest["distance"] = $distance;
break;
}
}
if($closest["distance"] != 1000)
{
$result["x"] = $closest["x"];
$result["y"] = $closest["y"];
$result["found"] = true;
return $result;
}
}
return $result;
}
As you can see, I'm no PHP expert, I didn't know there was a built in function to get the rgb channels, oops!
Final Call
So now that the algorithm ran, let's see what it found using the following code:
foreach($regions as $region)
{
echo "Potentially between " . $region["left"] . "," . $region["bottom"] . " and " . $region["right"] . "," . $region["top"] . "\n";
}
imagejpeg($large2, "final.jpg");
imagedestroy($large2);
The output (which is pretty close to the real solution):
Potentially between 108,380 and 867,827
in 7.9796848297119 seconds
Giving this picture (the rectangle between 108,380 and 867,827 is drawn in green)
Hope this helps!
My solution work if there is no color (except white and black around the image, but you can modify the script to get it work differently)
$width = imagesx($this->img_src);
$height = imagesy($this->img_src);
// navigate through pixels of image
for ($y = 0; $y < $height; $y++) {
for ($x=0; $x < $width; $x++) {
list($r, $g, $b) = imagergbat($this->img_src, $x, $y);
$black = 0.1;
$white = 0.9;
// calculate if the color is next to white or black, if not register it as a good pixel
$gs = (($r / 3) + ($g / 3) + ($b / 3);
$first_pixel = array();
if ($gs > $white && $gs < $black) {
// get coordinate of first pixel (left top)
if (empty($first_pixel))
$first_pixel = array($x, $y);
// And save last_pixel each time till the last one
$last_pixel = array($x, $y);
}
}
}
And you get the coordinates of your image. You have just to crop it after this.
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).
How can I check for a pixel pattern in PHP?
I mean I wanna use as condition that pixel A has xxx value and the following pixel B has another value yyy.
This is what I wrote:
$img = imagecreatefrompng("myimage.png");
$w = imagesx($img);
$h = imagesy($img);
for($y=0;$y<$h;$y++) {
for($x=0;$x<$w;$x++) {
$rgb = imagecolorat($img, $x, $y);
$r = ($rgb >> 16) & 0xFF;
$g = ($rgb >> 8) & 0xFF;
$b = $rgb & 0xFF;
echo "#".$r.$g.$b.",";
$pixel = $r.$g.$b;
if ($pixel == "481023" and $pixel+1???
}
echo "<br />\r\n";
}
I'd like to ask also if I can speed up the whole thing by incrementing the $x value by 2 every for cycle. This because I have a pattern of 2 pixels, maybe I can use something like:
for($x=0;$x<$w;$x+2) {
//...
if ($pixel == "xxx") {//check the following pixel}
else if ($pixel == "yyy") {//check the previous pixel}
}
You might want to define a function like:
function getpixelat($img,$x,$y) {
$rgb = imagecolorat($img,$x,$y);
$r = dechex(($rgb >> 16) & 0xFF);
$g = dechex(($rgb >> 8) & 0xFF);
$b = dechex($rgb & 0xFF);
return $r.$g.$b;
}
Notice the dechex - you need this if you want it to look like an HTML colour code. Otherwise "white" would be 255255255 instead of ffffff and you'd also get ambiguous colours - is 202020 a dark gray (20,20,20) or "red with a slight hint of blue" (202,0,20)?
Once you have this, it should be a simple matter:
for( $y=0; $y<$h; $y++) {
for( $x=0; $x<$w; $x++) {
$pixel = getpixelat($img,$x,$y);
if( $pixel == "481023" && getpixelat($img,$x+1,$y) == "998877") {
// pattern! Do something here.
$x++; // increment X so we don't bother checking the next pixel again.
}
}
}