Ask Your Question
0

Thresholding + masking image erratic results

asked 2014-10-01 01:15:55 -0600

JNI gravatar image

updated 2015-10-13 08:05:00 -0600

Hi! I'm trying to threshold + mask this image:

Orgiginal image

However, using the code below this produces erratic and random images: Erratic image #1 Erratic image #2 Erratic image #3

I'm guessing it has something to do with the binary header imprinted in beginning of the produced images, however, I'm not where they are coming from. Also, considering that the images change randomly, the header is to some extent unique for each image (EDIT: To clarify: using same code and input image, the output is not consistent for each conversion).

<UPDATE>: This is the part of the code which errs:

mask:
[255 255   0   0]
image:
[200 150 100  50]
result of cv2.bitwise_and(b,b,mask=a):
[[200]
 [150]
 [145]
 [  1]]

Now I'd assume the operation for e.g. element at index 1 (150) is evaluated as follows:

Image       ∧       Image       =   Image∧Image
150         ∧       150         =   150
0b10010110  ∧       0b10010110  =   0b10010110

⇒Image∧Image=Image

Image∧Image ∧       Mask        =   Image∧Image∧Mask
150         ∧       255         =   150
0b10010110  ∧       0b11111111  =   0b10010110

⇒Image∧Image∧Mask=Image∧Mask

And that holds true. However, this is what I get for element at index 2 (100), at different (but with identical parameters) function calls:

Image       ∧       Mask        =   Image∧Mask
100         ∧       0           =   184
0b10010110  ∧       0b11111111  =   0b10111000

and

Image       ∧       Mask        =   Image∧Mask
100         ∧       0           =   153
0b10010110  ∧       0b11111111  =   0b10011001

and

Image       ∧       Mask        =   Image∧Mask
100         ∧       0           =   49
0b10010110  ∧       0b11111111  =   0b00110001

What am I missing here?

</UPDATE>

The input image is an RGB (no alpha) and the thresholds are all positive.

Any insights are most welcome!

(EDIT: As per comments, the full code may be a bit verbose - so now listing relevant pieces first, full code below:)

def threshold_image(self,image):
  # image = PIL RGB image
  assert(image.size == (258,258))
  assert(len(image.getbands()) == 3)

  image=numpy.array(image) # Still RGB, not BGR.

  assert(image.shape == (258,258,3))

  mask = cv2.inRange(image, numpy.array([0, 0, 0], dtype=numpy.uint8), 
                      numpy.array([200, 250, 251], dtype=numpy.uint8))

  im=cv2.bitwise_and(image,image,mask=mask)
  assert(image.shape == (258,258,3))

  return Image.fromarray(im)

Full code:

from PIL import Image, ImageMath
import cv2, numpy, math, os, copy

A=0
B=1
C=2
MIN=0
MAX=1
U=0
L=1


class Thresholder():
  """Class for performing thresholding and/or conversions."""
  # _thresholds=[]
  # bounds=[[],[]]
  # conversion_factor = None
  # neg_test = False
  # pos_test = False

  def __init__(self,thresholds,from_cs="rgb",to_cs="rgb"):
    """Instantiates a new thresholding engine.

    Args:
      thresholds: 2d array of the form
        [   [cs_val_min_a,  cs_val_max_a],
          [cs_val_min_b,  cs_val_max_b],
          [cs_val_min_c,  cs_val_max_c]   ]

      ...where a,b and c are the channels of 
        the target (threshold) colorspace.

      Negative value in either max or min of a channel
        will trigger a NOT threshold (i.e. inverted).


      from_colorspace: Originating test set colorspace.
      to_colorspace: Colorspace of threshold values.

    Returns:
      A new Thresholding object.
    """

    self._thresholds=thresholds
    from_cs=from_cs.upper()
    to_cs=to_cs.upper()
    self.conversion_factor = self._check_cs_conversion(from_cs,to_cs)
    upper=(thresholds[A][MAX],thresholds[B][MAX],thresholds[C][MAX])
    lower=(thresholds[A][MIN],thresholds[B][MIN],thresholds[C][MIN])
    self.bounds=(upper,lower)
    upper = []
    lower = []
    self.inverts=[]

    for i in range(3):
      u=self.bounds ...
(more)
edit retag flag offensive close merge delete

Comments

Since this code is not standard OpenCV provided functionality but rather a function you just copy pasted from somewhere without knowing exactly what it is doing, I am afraid that the support for this will be minimal. Why not contact the original author for this?

StevenPuttemans gravatar imageStevenPuttemans ( 2014-10-01 02:32:38 -0600 )edit
1

From my point of view is a problem of Mat types/channels/size

thdrksdfthmn gravatar imagethdrksdfthmn ( 2014-10-01 02:40:40 -0600 )edit
1

@StevenPuttemans - Copy/Pasted from my code, yes. I am the 'original author'. How is this not standard functionality? Not saying it definitely isn't, just wondering why it wouldn't be. I'll edit post to show precisely which methods/operations are relevant, just to be safe :+) @thdrksdfthmn Yes, dimensionality may be cause, but where does it get the hickups?

JNI gravatar imageJNI ( 2014-10-01 03:24:47 -0600 )edit

You can do a debugging with imshow()s to see each step. I have not used python :)

thdrksdfthmn gravatar imagethdrksdfthmn ( 2014-10-01 06:20:41 -0600 )edit

@thdrksdfthmn: Thnx for tips. The corruption appears when applying the mask - at the bitwise_and. None image instances ahead of that are corrupt.

JNI gravatar imageJNI ( 2014-10-01 07:50:26 -0600 )edit

So, maybe the two images are not the same type? (you have 2 bitwise_and()s)

thdrksdfthmn gravatar imagethdrksdfthmn ( 2014-10-01 09:51:15 -0600 )edit

image.shape == (258,258,3) mask.shape == (258,258) Since the mask is binary, the colorspace of this shouldn't matter, no?

JNI gravatar imageJNI ( 2014-10-01 10:27:36 -0600 )edit

No, the mask is always one channel. See this, you have two sources and a destination Mat

thdrksdfthmn gravatar imagethdrksdfthmn ( 2014-10-01 10:43:25 -0600 )edit

Yes, the mask is one channel. mask.shape == (258, 258) implies (258,258,1). Sorry for not being clear. If I can supply any other helpful information let me know. I've solved the issue for now by using a min/max operation on the image and a 3-channel version of the 1 channel mask mentioned above.

JNI gravatar imageJNI ( 2014-10-01 13:58:08 -0600 )edit

2 answers

Sort by » oldest newest most voted
2

answered 2014-10-07 07:42:22 -0600

JNI gravatar image

As mentioned in discussion under my other answer, the problems most likely arose from either a bug or corruption in the OpenCV-library I used (v2.3.1-11, prebuilt from 64bit Debian Wheezy repositories).

Building latest version from source fixed my issues, although the method for mask/threshold described in my other answer seems easier and more semantic code wise.

ThresholdedImage=(Original<ThresholdsMAX & Original>ThresholdsMIN) * Original
edit flag offensive delete link more

Comments

Good to have found the solution. Nevertheless it is always a good idea to grab the latest stable release instead of using old prebuilt versions. Thumbs up!

StevenPuttemans gravatar imageStevenPuttemans ( 2014-10-07 07:46:23 -0600 )edit
1

I agree, but sometimes convenience conquers all ;-)

JNI gravatar imageJNI ( 2014-10-07 08:24:11 -0600 )edit
0

answered 2014-10-04 07:04:05 -0600

JNI gravatar image

updated 2014-10-06 12:46:20 -0600

TLDR;

ThresholdedImage=(Original<ThresholdsMAX & Original>ThresholdsMIN) * Original

LONG VERSION;

Ok,

I have still to figure out why the results are not consistent between each function call (help?) but my method of thresholding and masking seems to be either horrendously outdated or just plain wrong.

I was looking for methods on how to perform thresholding, and the closest thing I found was by using the cv2.inRange() function. Furthermore, my method of applying a mask was to do a cv2.bitwise_and() comparison on the original image whilst using a mask argument using image from previous operation (see details in main post).

This was overly complicated, whereas the code I needed was simply:

ThresholdedImage=(Original<ThresholdsMAX & Original>ThresholdsMIN) * Original

Where ThresholdedImage retains original background.

Hope this helps someone else...

edit flag offensive delete link more

Comments

1

IMHO, there is something wrong with your bitwise_and, because of those 0 case of mask, could it be a problem of the version you use (it is some kind of undefined behaviour)? Are you using OpenCV 3.0.0? In fact I have not tested your code, but I use the function (without mask) and it is working well. I use 2.4.9 on C++

thdrksdfthmn gravatar imagethdrksdfthmn ( 2014-10-06 03:12:02 -0600 )edit

Thank you for your reply, @thdrksdfthmn (btw - did you make that nick only to make it hard to spell? ;P ). It's possible. I'm thinking it may be a threading issue too...? To be frank I'm clueless, for even between single, individual processes output can vary. Both opencv-core and python wrapper are v2.3.1-11, prebuilt from 64bit Debian Wheezy repositories.

JNI gravatar imageJNI ( 2014-10-06 09:29:05 -0600 )edit
2

Just tested the above script on a fresh download of windows binaries, and it shows no corruption. Must be my install then, or a fixed bug in an earlier version (v2.3.1 vs. v2.4.9). I'll try doing a fresh build on my Debian box. UPDATE: Confirmed. Fresh build of 2.4.9 on the same system works flawlessly. Thanks for guidance! Should I make a separate answer for this?

JNI gravatar imageJNI ( 2014-10-06 11:37:43 -0600 )edit

My nick is from my highschool mail, I did not wanted to use my true mail for forums. It is hard, but you can figure out what it means. And btw, yes, you can do a new answer for better visibility.

thdrksdfthmn gravatar imagethdrksdfthmn ( 2014-10-07 02:43:34 -0600 )edit

Question Tools

Stats

Asked: 2014-10-01 01:15:55 -0600

Seen: 1,821 times

Last updated: Oct 07 '14