6
$\begingroup$

I want a function to return the intensity of one pixel notwithstanding the Color Space of the source image.

I wrote:

getIntensity[i_Image, pos_List] :=
 ColorConvert[Flatten@{PixelValue[i, pos]}, ImageColorSpace[i] -> "Grayscale"]

but it looks clumsy and there is probably a better way that I'm overlooking.

Test cases:

i = ExampleData[{"TestImage", "Lena"}];
ig = ColorConvert[i, "Grayscale"];
getIntensity[i, {1, 1}]
getIntensity[ig, {1, 1}]

(*
  {0.172275}
  {0.172549}
*)

Note: Not very important right now, but it would be nice if the position could be specified as a rectangular region {a ;; b, c ;; d}

$\endgroup$
6
  • $\begingroup$ {a ;; b, c ;; d} will return the the intensity of every pixel or a mean value? $\endgroup$
    – yode
    Mar 18 '16 at 6:34
  • $\begingroup$ @yode A list (matrix) with the intensities for every pixel $\endgroup$ Mar 18 '16 at 6:47
  • $\begingroup$ Incredible, how unterse it is to transform a Span specification to a Take specification! Working on this right now. $\endgroup$
    – LLlAMnYP
    Mar 18 '16 at 9:46
  • $\begingroup$ Here's a function. Should work in v9, though ColorConvert got updated in v10. f[i_Image, c_List] := ImageTake[i, Sequence @@ (Replace[ c, {h___, n_Integer, t___} :> {h, {n}, t}] /. Span -> List)] // ColorConvert[#, "Grayscale"] & // ImageData I'm afraid, taking a span specification forces me to use a very clumsy replacement rule. $\endgroup$
    – LLlAMnYP
    Mar 18 '16 at 9:49
  • 1
    $\begingroup$ The golfed down version that takes a Take kind of part specification looks like this: f[i_, c__] := ImageData@ColorConvert[ImageTake[i, c], "Grayscale"] $\endgroup$
    – LLlAMnYP
    Mar 18 '16 at 9:51
5
$\begingroup$

Reposting my comment as an answer.

This has the same caveat, as @yode mentioned: pixel coordinates (from top to bottom, then from left to right) vs "normal coordinates" (left to right, then upwards).

Perhaps this is terser, although it always returns a 2-d array, even if only of one element. The golfed down version doesn't take a Span:

f[i_, c__] := ImageData@ColorConvert[ImageTake[i, c], "Grayscale"]

Usage:

f[image, {row1, row2, step(optional)}, {col1, col2, step(optional)}]

the part specification works exactly like ImageTake.

The ungolfed version accepts Belisarius' {a ;; b, c ;; d} desire (also with the step size, if need be):

f[i_Image, c_List] := ImageTake[i,
  Sequence @@ (Replace[ c, {h___, n_Integer, t___} :> {h, {n}, t}] /. Span -> List)] // 
    ColorConvert[#, "Grayscale"] & // ImageData

The replacement rule to accept Span is rather clumsy and can probably be improved. An integer specification will be equivalent to n ;; n ;; 1.

If we extend the code as

f[i_, c__] := ImageData@ColorConvert[ImageTake[
                ImageRotate[i, -Pi/2], c], "Grayscale"]

(or similarly in the ungolfed version), we'll get the "normal" coordinates. In this case the part specification will be first the x-range, then the y-range from bottom to top. It might be counterintuitive though, because the returned array will be essentially the image rotated clockwise.

Adding in the discussion in the comments: the image standard coordinates know nothing about the negative specifications, so I can probably assume that the Span-like argument will take only positive values. Because ImageRotate is horribly inefficient to simply get a coordinate transformation, and @Belisarius hinted at the use of ImageTrim, there's a very simple workaround available (no 3-argument Span specification though, sorry).

f[i_, c:{(_Span|_Integer)..}] := ImageData@ColorConvert[ImageTrim[i, Thread[List@@@c]-.5], "Grayscale"]

I have no idea, why Belisarius' two approaches (convert to grayscale, then get pixel value or vice versa) give different results. Mine is consistent with getIntensity[ig, {1, 1}].

$\endgroup$
7
  • $\begingroup$ On the same line ImageData@ColorConvert[ImageTrim[i, {c - .5, c - .5}], "Grayscale"] uses image standard coordinates $\endgroup$ Mar 18 '16 at 13:56
  • $\begingroup$ Yes, that is vastly better. As @nikie alerted me over here, ImageRotate is a horribly inefficient way to do the coordinate transformation, although very straighforward. $\endgroup$
    – LLlAMnYP
    Mar 18 '16 at 14:10
  • $\begingroup$ @Dr.belisarius by the way, seeing as we're dealing with image standard coordinates, are negative specifications to refer to "{x,y} from last" at all relevant here? Because if not, there's a lot less corner cases to deal with, when implementing the expected behavior of Span. $\endgroup$
    – LLlAMnYP
    Mar 18 '16 at 14:13
  • $\begingroup$ I suspected the span thing could be cumbersome, so the final note on the question. I like your answer, let the span rest in peace. $\endgroup$ Mar 18 '16 at 14:20
  • 1
    $\begingroup$ @Dr.belisarius just did, though the answer is a bit cluttered now. No time to tidy up though, maybe some other time. $\endgroup$
    – LLlAMnYP
    Mar 18 '16 at 14:29
4
$\begingroup$
fun[i_Image, rect_List] := 
 ImageMeasurements[i, "MeanIntensity", 
    Masking -> SparseArray[{# -> 1}, ImageDimensions[i]]] & /@ 
  Catenate@CoordinateBoundingBoxArray[rect]

Usage:

i = ExampleData[{"TestImage", "Lena"}];
fun[i, {{2, 3}, {4, 5}}]

{0.635294, 0.631373, 0.635294, 0.635294, 0.631373, 0.635294, 0.635294, 0.631373, 0.635294}


Update: I'm very sorry I make a mistake in the Image Coordinate Systems,And the code above serve a Index Coordinates.So we cann't use SparseArray as a mask unless you have a need in Index Coordinates.But this time let's face up to a Pixel-aligned Image Coordinates

When your object is a list of pixel:

fun1[i_Image, pos_List] := 
 ImageMeasurements[i, "MeanIntensity", 
    Masking -> 
     ReplacePixelValue[
      ConstantImage[0, ImageDimensions[i]], {# -> 1}]] & /@ pos

Usage:

fun1[i, {{1, 1}(*your test case*), {5, 6}, {10, 10}}]
fun1[ig, {{1, 1}(*your test case*), {5, 6}, {10, 10}}]

{0.172549, 0.211765, 0.247059}

{0.172549, 0.211765, 0.247059}

If your object is a region of pixel rectange:

fun2[i_Image, rect_List] := 
 ImageMeasurements[i, "MeanIntensity", 
    Masking -> 
     ReplacePixelValue[
      ConstantImage[0, ImageDimensions[i]], {# -> 1}]] & /@ 
  Catenate@CoordinateBoundingBoxArray[rect]

Usage:

fun2[i, {{1, 1}, {5, 8}}]

{0.172549, 0.172549, 0.168627, 0.180392, 0.211765, 0.188235, 0.184314, 0.188235, 0.172549, 0.172549, 0.168627, 0.180392, 0.211765, 0.188235, 0.184314, 0.188235, 0.215686, 0.215686, 0.196078, 0.196078, 0.188235, 0.223529, 0.215686, 0.207843, 0.2, 0.2, 0.184314, 0.196078, 0.184314, 0.203922, 0.207843, 0.192157, 0.211765, 0.211765, 0.211765, 0.203922, 0.207843, 0.211765, 0.196078, 0.207843}

$\endgroup$
3
  • 3
    $\begingroup$ I meant "terser". Like in more "brief, short, to the point, concise, succinct, crisp" :) $\endgroup$ Mar 18 '16 at 7:05
  • $\begingroup$ Only these option/function have more letters.but is brief actually I think.aha~ $\endgroup$
    – yode
    Mar 18 '16 at 7:09
  • $\begingroup$ In any case, I upvoted without testing it (it is a bad practice!). But I'm in v9 and you're using v10 features, so no way to check. Could you include the results for my two test cases? $\endgroup$ Mar 18 '16 at 7:17

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.