# Thresholding + masking image erratic results Hi! I'm trying to threshold + mask this image: However, using the code below this produces erratic and random images:   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]
[


[  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

150         ∧       255         =   150
0b10010110  ∧       0b11111111  =   0b10010110



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))

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 ...
edit retag close merge delete

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?

1

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

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?

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

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

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

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

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

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.

Sort by » oldest newest most voted

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

more

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!

1

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...

more

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++

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.

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?

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.

Official site

GitHub

Wiki

Documentation