Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

10845 lines (8129 sloc) 387.396 kB
# Load required libraries
from SimpleCV.base import *
from SimpleCV.Color import *
class ColorSpace:
"""
**SUMMARY**
The colorspace class is used to encapsulate the color space of a given image.
This class acts like C/C++ style enumerated type.
See: http://stackoverflow.com/questions/2122706/detect-color-space-with-opencv
"""
UNKNOWN = 0
BGR = 1
GRAY = 2
RGB = 3
HLS = 4
HSV = 5
XYZ = 6
YCrCb = 7
class ImageSet(list):
"""
**SUMMARY**
This is an abstract class for keeping a list of images. It has a few
advantages in that you can use it to auto load data sets from a directory
or the net.
Keep in mind it inherits from a list too, so all the functionality a
normal python list has this will too.
**EXAMPLES**
>>> imgs = ImageSet()
>>> imgs.download("ninjas")
>>> imgs.show(ninjas)
or you can load a directory path:
>>> imgs = ImageSet('/path/to/imgs/')
>>> imgs.show()
This will download and show a bunch of random ninjas. If you want to
save all those images locally then just use:
>>> imgs.save()
You can also load up the sample images that come with simplecv as:
>>> imgs = ImageSet('samples')
>>> imgs.filelist
>>> logo = imgs.find('simplecv.png')
**TO DO**
Eventually this should allow us to pull image urls / paths from csv files.
The method also allow us to associate an arbitraty bunch of data with each
image, and on load/save pickle that data or write it to a CSV file.
"""
filelist = None
def __init__(self, directory = None):
if not directory:
return
if directory.lower() == 'samples' or directory.lower() == 'sample':
#~ import pdb
#~ pdb.set_trace()
pth = __file__
if sys.platform.lower() == 'win32' or sys.platform.lower() == 'win64':
pth = pth.split('\\')[-2]
else:
pth = pth.split('/')[-2]
pth = os.path.realpath(pth)
directory = os.path.join(pth, 'sampleimages')
self.load(directory)
def download(self, tag=None, number=10, size='thumb'):
"""
**SUMMARY**
This function downloads images from Google Image search based
on the tag you provide. The number is the number of images you
want to have in the list. Valid values for size are 'thumb', 'small',
'medium', 'large' or a tuple of exact dimensions i.e. (640,480).
Note that 'thumb' is exceptionally faster than others.
.. Warning::
This requires the python library Beautiful Soup to be installed
http://www.crummy.com/software/BeautifulSoup/
**PARAMETERS**
* *tag* - A string of tag values you would like to download.
* *number* - An integer of the number of images to try and download.
* *size* - the size of the images to download. Valid options a tuple
of the exact size or a string of the following approximate sizes:
* thumb ~ less than 128x128
* small ~ approximately less than 640x480 but larger than 128x128
* medium ~ approximately less than 1024x768 but larger than 640x480.
* large ~ > 1024x768
**RETURNS**
Nothing - but caches local copy of images.
**EXAMPLE**
>>> imgs = ImageSet()
>>> imgs.download("ninjas")
>>> imgs.show(ninjas)
"""
try:
from BeautifulSoup import BeautifulSoup
except:
print "You need to install Beatutiul Soup to use this function"
print "to install you can use:"
print "easy_install beautifulsoup"
return
INVALID_SIZE_MSG = """I don't understand what size images you want.
Valid options: 'thumb', 'small', 'medium', 'large'
or a tuple of exact dimensions i.e. (640,480)."""
if isinstance(size, basestring):
size = size.lower()
if size == 'thumb':
size_param = ''
elif size == 'small':
size_param = '&tbs=isz:s'
elif size == 'medium':
size_param = '&tbs=isz:m'
elif size == 'large':
size_param = '&tbs=isz:l'
else:
print INVALID_SIZE_MSG
return None
elif type(size) == tuple:
width, height = size
size_param = '&tbs=isz:ex,iszw:' + str(width) + ',iszh:' + str(height)
else:
print INVALID_SIZE_MSG
return None
# Used to extract imgurl parameter value from a URL
imgurl_re = re.compile('(?<=(&|\?)imgurl=)[^&]*((?=&)|$)')
add_set = ImageSet()
candidate_count = 0
while len(add_set) < number:
opener = urllib2.build_opener()
opener.addheaders = [('User-agent', 'Mozilla/5.0')]
url = ("http://www.google.com/search?tbm=isch&q=" + urllib2.quote(tag) +
size_param + "&start=" + str(candidate_count))
page = opener.open(url)
soup = BeautifulSoup(page)
img_urls = []
# Gets URLs of the thumbnail images
if size == 'thumb':
imgs = soup.findAll('img')
for img in imgs:
dl_url = str(dict(img.attrs)['src'])
img_urls.append(dl_url)
# Gets the direct image URLs
else:
for link_tag in soup.findAll('a', {'href': re.compile('imgurl=')}):
dirty_url = link_tag.get('href') # URL to an image as given by Google Images
dl_url = str(re.search(imgurl_re, dirty_url).group()) # The direct URL to the image
img_urls.append(dl_url)
for dl_url in img_urls:
try:
add_img = Image(dl_url, verbose=False)
# Don't know a better way to check if the image was actually returned
if add_img.height <> 0 and add_img.width <> 0:
add_set.append(add_img)
except:
#do nothing
None
if len(add_set) >= number:
break
self.extend(add_set)
def show(self, showtime = 0.25):
"""
**SUMMARY**
This is a quick way to show all the items in a ImageSet.
The time is in seconds. You can also provide a decimal value, so
showtime can be 1.5, 0.02, etc.
to show each image.
**PARAMETERS**
* *showtime* - the time, in seconds, to show each image in the set.
**RETURNS**
Nothing.
**EXAMPLE**
>>> imgs = ImageSet()
>>> imgs.download("ninjas")
>>> imgs.show()
"""
for i in self:
i.show()
time.sleep(showtime)
def _get_app_ext(self, loops=0):
""" Application extention. Part that secifies amount of loops.
if loops is 0, if goes on infinitely.
"""
bb = "\x21\xFF\x0B" # application extension
bb += "NETSCAPE2.0"
bb += "\x03\x01"
if loops == 0:
loops = 2**16-1
bb += int_to_bin(loops)
bb += '\x00' # end
return bb
def _get_graphics_control_ext(self, duration=0.1):
""" Graphics Control Extension. A sort of header at the start of
each image. Specifies transparancy and duration. """
bb = '\x21\xF9\x04'
bb += '\x08' # no transparency
bb += int_to_bin( int(duration*100) ) # in 100th of seconds
bb += '\x00' # no transparent color
bb += '\x00' # end
return bb
def _write_gif(self, filename, duration=0.1, loops=0, dither=1):
""" Given a set of images writes the bytes to the specified stream.
"""
frames = 0
previous = None
fp = open(filename, 'wb')
if not PIL_ENABLED:
logger.warning("Need PIL to write animated gif files.")
return
converted = []
for img in self:
if not isinstance(img,pil.Image):
pil_img = img.getPIL()
else:
pil_img = img
converted.append((pil_img.convert('P',dither=dither), img._get_header_anim()))
#try:
for img, header_anim in converted:
if not previous:
# gather data
palette = getheader(img)[1]
data = getdata(img)
imdes, data = data[0], data[1:]
header = header_anim
appext = self._get_app_ext(loops)
graphext = self._get_graphics_control_ext(duration)
# write global header
fp.write(header)
fp.write(palette)
fp.write(appext)
# write image
fp.write(graphext)
fp.write(imdes)
for d in data:
fp.write(d)
else:
# gather info (compress difference)
data = getdata(img)
imdes, data = data[0], data[1:]
graphext = self._get_graphics_control_ext(duration)
# write image
fp.write(graphext)
fp.write(imdes)
for d in data:
fp.write(d)
previous = img.copy()
frames = frames + 1
fp.write(";") # end gif
#finally:
# fp.close()
# return frames
def save(self, destination=None, dt=0.2, verbose = False, displaytype=None):
"""
**SUMMARY**
This is a quick way to save all the images in a data set.
Or to Display in webInterface.
If you didn't specify a path one will randomly be generated.
To see the location the files are being saved to then pass
verbose = True.
**PARAMETERS**
* *destination* - path to which images should be saved, or name of gif
* file. If this ends in .gif, the pictures will be saved accordingly.
* *dt* - time between frames, for creating gif files.
* *verbose* - print the path of the saved files to the console.
* *displaytype* - the method use for saving or displaying images.
valid values are:
* 'notebook' - display to the ipython notebook.
* None - save to a temporary file.
**RETURNS**
Nothing.
**EXAMPLE**
>>> imgs = ImageSet()
>>> imgs.download("ninjas")
>>> imgs.save(destination="ninjas_folder", verbose=True)
>>> imgs.save(destination="ninjas.gif", verbose=True)
"""
if displaytype=='notebook':
try:
from IPython.core.display import Image as IPImage
except ImportError:
print "You need IPython Notebooks to use this display mode"
return
from IPython.core import display as Idisplay
for i in self:
tf = tempfile.NamedTemporaryFile(suffix=".png")
loc = '/tmp/' + tf.name.split('/')[-1]
tf.close()
i.save(loc)
Idisplay.display(IPImage(filename=loc))
return
else:
if destination:
if destination.endswith(".gif"):
self._write_gif(destination, dt)
else:
for i in self:
i.save(path=destination, temp=True, verbose=verbose)
else:
for i in self:
i.save(verbose=verbose)
def showPaths(self):
"""
**SUMMARY**
This shows the file paths of all the images in the set.
If they haven't been saved to disk then they will not have a filepath
**RETURNS**
Nothing.
**EXAMPLE**
>>> imgs = ImageSet()
>>> imgs.download("ninjas")
>>> imgs.save(verbose=True)
>>> imgs.showPaths()
**TO DO**
This should return paths as a list too.
"""
for i in self:
print i.filename
def _read_gif(self, filename):
""" read_gif(filename)
Reads images from an animated GIF file. Returns the number of images loaded.
"""
if not PIL_ENABLED:
return
elif not os.path.isfile(filename):
return
pil_img = pil.open(filename)
pil_img.seek(0)
pil_images = []
try:
while True:
pil_images.append(pil_img.copy())
pil_img.seek(pil_img.tell()+1)
except EOFError:
pass
loaded = 0
for img in pil_images:
self.append(Image(img))
loaded += 1
return loaded
def load(self, directory = None, extension = None):
"""
**SUMMARY**
This function loads up files automatically from the directory you pass
it. If you give it an extension it will only load that extension
otherwise it will try to load all know file types in that directory.
extension should be in the format:
extension = 'png'
**PARAMETERS**
* *directory* - The path or directory from which to load images. If this
* ends with .gif, it'll read from the gif file accordingly.
* *extension* - The extension to use. If none is given png is the default.
**RETURNS**
The number of images in the image set.
**EXAMPLE**
>>> imgs = ImageSet()
>>> imgs.load("images/faces")
>>> imgs.load("images/eyes", "png")
"""
if not directory:
print "You need to give a directory to load from"
return
elif directory.endswith(".gif"):
return self._read_gif(directory)
if not os.path.exists(directory):
print "Invalid image path given"
return
if extension:
extension = "*." + extension
formats = [os.path.join(directory, extension)]
else:
formats = [os.path.join(directory, x) for x in IMAGE_FORMATS]
file_set = [glob.glob(p) for p in formats]
self.filelist = dict()
for f in file_set:
for i in f:
tmp = Image(i)
if sys.platform.lower() == 'win32' or sys.platform.lower() == 'win64':
self.filelist[tmp.filename.split('\\')[-1]] = tmp
else:
self.filelist[tmp.filename.split('/')[-1]] = tmp
self.append(tmp)
return len(self)
class Image:
"""
**SUMMARY**
The Image class is the heart of SimpleCV and allows you to convert to and
from a number of source types with ease. It also has intelligent buffer
management, so that modified copies of the Image required for algorithms
such as edge detection, etc can be cached and reused when appropriate.
Image are converted into 8-bit, 3-channel images in RGB colorspace. It will
automatically handle conversion from other representations into this
standard format. If dimensions are passed, an empty image is created.
**EXAMPLE**
>>> i = Image("/path/to/image.png")
>>> i = Camera().getImage()
You can also just load the SimpleCV logo using:
>>> img = Image("simplecv")
>>> img = Image("logo")
>>> img = Image("logo_inverted")
>>> img = Image("logo_transparent")
Or you can load an image from a URL:
>>> img = Image("http://www.simplecv.org/image.png")
"""
width = 0 #width and height in px
height = 0
depth = 0
filename = "" #source filename
filehandle = "" #filehandle if used
camera = ""
_mLayers = []
_mDoHuePalette = False
_mPaletteBins = None
_mPalette = None
_mPaletteMembers = None
_mPalettePercentages = None
_barcodeReader = "" #property for the ZXing barcode reader
#these are buffer frames for various operations on the image
_bitmap = "" #the bitmap (iplimage) representation of the image
_matrix = "" #the matrix (cvmat) representation
_grayMatrix = "" #the gray scale (cvmat) representation -KAS
_graybitmap = "" #a reusable 8-bit grayscale bitmap
_equalizedgraybitmap = "" #the above bitmap, normalized
_blobLabel = "" #the label image for blobbing
_edgeMap = "" #holding reference for edge map
_cannyparam = (0, 0) #parameters that created _edgeMap
_pil = "" #holds a PIL object in buffer
_numpy = "" #numpy form buffer
_grayNumpy = "" # grayscale numpy for keypoint stuff
_colorSpace = ColorSpace.UNKNOWN #Colorspace Object
_pgsurface = ""
_cv2Numpy = None #numpy array for OpenCV >= 2.3
_cv2GrayNumpy = None #grayscale numpy array for OpenCV >= 2.3
#For DFT Caching
_DFT = [] #an array of 2 channel (real,imaginary) 64F images
#Keypoint caching values
_mKeyPoints = None
_mKPDescriptors = None
_mKPFlavor = "NONE"
#temp files
_tempFiles = []
#when we empty the buffers, populate with this:
_initialized_buffers = {
"_bitmap": "",
"_matrix": "",
"_grayMatrix": "",
"_graybitmap": "",
"_equalizedgraybitmap": "",
"_blobLabel": "",
"_edgeMap": "",
"_cannyparam": (0, 0),
"_pil": "",
"_numpy": "",
"_grayNumpy":"",
"_pgsurface": ""}
def __repr__(self):
if len(self.filename) == 0:
fn = "None"
else:
fn = self.filename
return "<SimpleCV.Image Object size:(%d, %d), filename: (%s), at memory location: (%s)>" % (self.width, self.height, fn, hex(id(self)))
#initialize the frame
#parameters: source designation (filename)
#todo: handle camera/capture from file cases (detect on file extension)
def __init__(self, source = None, camera = None, colorSpace = ColorSpace.UNKNOWN,verbose=True, sample=False, cv2image=False):
"""
**SUMMARY**
The constructor takes a single polymorphic parameter, which it tests
to see how it should convert into an RGB image. Supported types include:
**PARAMETERS**
* *source* - The source of the image. This can be just about anything, a numpy arrray, a file name, a width and height
tuple, a url. Certain strings such as "lenna" or "logo" are loaded automatically for quick testing.
* *camera* - A camera to pull a live image.
* *colorspace* - A default camera color space. If none is specified this will usually default to the BGR colorspace.
* *sample* - This is set to true if you want to load some of the included sample images without having to specify the complete path
**EXAMPLES**
>>> img = Image('simplecv')
>>> img = Image('test.png')
>>> img = Image('http://www.website.com/my_image.jpg')
>>> img.show()
**NOTES**
OpenCV: iplImage and cvMat types
Python Image Library: Image type
Filename: All opencv supported types (jpg, png, bmp, gif, etc)
URL: The source can be a url, but must include the http://
"""
self._mLayers = []
self.camera = camera
self._colorSpace = colorSpace
#Keypoint Descriptors
self._mKeyPoints = []
self._mKPDescriptors = []
self._mKPFlavor = "NONE"
#Pallete Stuff
self._mDoHuePalette = False
self._mPaletteBins = None
self._mPalette = None
self._mPaletteMembers = None
self._mPalettePercentages = None
#Temp files
self._tempFiles = []
#Check if need to load from URL
#(this can be made shorter)if type(source) == str and (source[:7].lower() == "http://" or source[:8].lower() == "https://"):
if isinstance(source, basestring) and (source.lower().startswith("http://") or source.lower().startswith("https://")):
#try:
# added spoofed user agent for images that are blocking bots (like wikipedia)
req = urllib2.Request(source, headers={'User-Agent' : "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_4) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.54 Safari/536.5"})
img_file = urllib2.urlopen(req)
#except:
#if verbose:
#print "Couldn't open Image from URL:" + source
#return None
im = StringIO(img_file.read())
source = pil.open(im).convert("RGB")
#This section loads custom built-in images
if isinstance(source, basestring):
tmpname = source.lower()
if tmpname == "simplecv" or tmpname == "logo":
imgpth = os.path.join(LAUNCH_PATH, 'sampleimages','simplecv.png')
source = imgpth
elif tmpname == "simplecv_inverted" or tmpname == "inverted" or tmpname == "logo_inverted":
imgpth = os.path.join(LAUNCH_PATH, 'sampleimages','simplecv_inverted.png')
source = imgpth
elif tmpname == "lenna":
imgpth = os.path.join(LAUNCH_PATH, 'sampleimages','lenna.png')
source = imgpth
elif sample:
imgpth = os.path.join(LAUNCH_PATH, 'sampleimages', source)
source = imgpth
if (type(source) == tuple):
w = int(source[0])
h = int(source[1])
source = cv.CreateImage((w,h), cv.IPL_DEPTH_8U, 3)
cv.Zero(source)
if (type(source) == cv.cvmat):
self._matrix = source
if((source.step/source.cols)==3): #this is just a guess
self._colorSpace = ColorSpace.BGR
elif((source.step/source.cols)==1):
self._colorSpace = ColorSpace.BGR
else:
self._colorSpace = ColorSpace.UNKNOWN
elif (type(source) == np.ndarray): #handle a numpy array conversion
if (type(source[0, 0]) == np.ndarray): #we have a 3 channel array
#convert to an iplimage bitmap
source = source.astype(np.uint8)
self._numpy = source
if not cv2image:
invertedsource = source[:, :, ::-1].transpose([1, 0, 2])
else:
# If the numpy array is from cv2, then it must not be transposed.
invertedsource = source
invertedsource = source[:, :, ::-1].transpose([1, 0, 2])
self._bitmap = cv.CreateImageHeader((invertedsource.shape[1], invertedsource.shape[0]), cv.IPL_DEPTH_8U, 3)
cv.SetData(self._bitmap, invertedsource.tostring(),
invertedsource.dtype.itemsize * 3 * invertedsource.shape[1])
self._colorSpace = ColorSpace.BGR #this is an educated guess
else:
#we have a single channel array, convert to an RGB iplimage
source = source.astype(np.uint8)
source = source.transpose([1,0]) #we expect width/height but use col/row
self._bitmap = cv.CreateImage((source.shape[1], source.shape[0]), cv.IPL_DEPTH_8U, 3)
channel = cv.CreateImageHeader((source.shape[1], source.shape[0]), cv.IPL_DEPTH_8U, 1)
#initialize an empty channel bitmap
cv.SetData(channel, source.tostring(),
source.dtype.itemsize * source.shape[1])
cv.Merge(channel, channel, channel, None, self._bitmap)
self._colorSpace = ColorSpace.BGR
elif (type(source) == cv.iplimage):
if (source.nChannels == 1):
self._bitmap = cv.CreateImage(cv.GetSize(source), cv.IPL_DEPTH_8U, 3)
cv.Merge(source, source, source, None, self._bitmap)
self._colorSpace = ColorSpace.BGR
else:
self._bitmap = source
self._colorSpace = ColorSpace.BGR
elif (type(source) == type(str())):
if source == '':
raise IOError("No filename provided to Image constructor")
elif source.split('.')[-1] == 'webp':
try:
from webm import decode as webmDecode
except ImportError:
logger.warning('The webm module needs to be installed to load webp files: https://github.com/ingenuitas/python-webm')
return
WEBP_IMAGE_DATA = bytearray(file(source, "rb").read())
result = webmDecode.DecodeRGB(WEBP_IMAGE_DATA)
webpImage = pil.frombuffer(
"RGB", (result.width, result.height), str(result.bitmap),
"raw", "RGB", 0, 1
)
self._pil = webpImage.convert("RGB")
self._bitmap = cv.CreateImageHeader(self._pil.size, cv.IPL_DEPTH_8U, 3)
self.filename = source
cv.SetData(self._bitmap, self._pil.tostring())
cv.CvtColor(self._bitmap, self._bitmap, cv.CV_RGB2BGR)
else:
self.filename = source
try:
self._bitmap = cv.LoadImage(self.filename, iscolor=cv.CV_LOAD_IMAGE_COLOR)
except:
self._pil = pil.open(self.filename).convert("RGB")
self._bitmap = cv.CreateImageHeader(self._pil.size, cv.IPL_DEPTH_8U, 3)
cv.SetData(self._bitmap, self._pil.tostring())
cv.CvtColor(self._bitmap, self._bitmap, cv.CV_RGB2BGR)
#TODO, on IOError fail back to PIL
self._colorSpace = ColorSpace.BGR
elif (type(source) == pg.Surface):
self._pgsurface = source
self._bitmap = cv.CreateImageHeader(self._pgsurface.get_size(), cv.IPL_DEPTH_8U, 3)
cv.SetData(self._bitmap, pg.image.tostring(self._pgsurface, "RGB"))
cv.CvtColor(self._bitmap, self._bitmap, cv.CV_RGB2BGR)
self._colorSpace = ColorSpace.BGR
elif (PIL_ENABLED and (
(len(source.__class__.__bases__) and source.__class__.__bases__[0].__name__ == "ImageFile")
or source.__class__.__name__ == "JpegImageFile"
or source.__class__.__name__ == "WebPPImageFile"
or source.__class__.__name__ == "Image")):
if source.mode != 'RGB':
source = source.convert('RGB')
self._pil = source
#from the opencv cookbook
#http://opencv.willowgarage.com/documentation/python/cookbook.html
self._bitmap = cv.CreateImageHeader(self._pil.size, cv.IPL_DEPTH_8U, 3)
cv.SetData(self._bitmap, self._pil.tostring())
self._colorSpace = ColorSpace.BGR
cv.CvtColor(self._bitmap, self._bitmap, cv.CV_RGB2BGR)
#self._bitmap = cv.iplimage(self._bitmap)
else:
return None
#if the caller passes in a colorspace we overide it
if(colorSpace != ColorSpace.UNKNOWN):
self._colorSpace = colorSpace
bm = self.getBitmap()
self.width = bm.width
self.height = bm.height
self.depth = bm.depth
def __del__(self):
"""
This is called when the instance is about to be destroyed also called a destructor.
"""
try :
for i in self._tempFiles:
if (isinstance(i,str)):
os.remove(i)
except :
pass
def getEXIFData(self):
"""
**SUMMARY**
This function extracts the exif data from an image file like JPEG or TIFF. The data is returned as a dict.
**RETURNS**
A dictionary of key value pairs. The value pairs are defined in the EXIF.py file.
**EXAMPLE**
>>> img = Image("./SimpleCV/sampleimages/OWS.jpg")
>>> data = img.getEXIFData()
>>> data['Image GPSInfo'].values
**NOTES**
* Compliments of: http://exif-py.sourceforge.net/
* See also: http://en.wikipedia.org/wiki/Exchangeable_image_file_format
**See Also**
:py:class:`EXIF`
"""
import os, string
if( len(self.filename) < 5 or self.filename is None ):
#I am not going to warn, better of img sets
#logger.warning("ImageClass.getEXIFData: This image did not come from a file, can't get EXIF data.")
return {}
fileName, fileExtension = os.path.splitext(self.filename)
fileExtension = string.lower(fileExtension)
if( fileExtension != '.jpeg' and fileExtension != '.jpg' and
fileExtension != 'tiff' and fileExtension != '.tif'):
#logger.warning("ImageClass.getEXIFData: This image format does not support EXIF")
return {}
raw = open(self.filename,'rb')
data = process_file(raw)
return data
def live(self):
"""
**SUMMARY**
This shows a live view of the camera.
* Left click will show mouse coordinates and color.
* Right click will kill the live image.
**RETURNS**
Nothing. In place method.
**EXAMPLE**
>>> cam = Camera()
>>> cam.live()
"""
start_time = time.time()
from SimpleCV.Display import Display
i = self
d = Display(i.size())
i.save(d)
col = Color.RED
while d.isNotDone():
i = self
i.clearLayers()
elapsed_time = time.time() - start_time
if d.mouseLeft:
txt = "coord: (" + str(d.mouseX) + "," + str(d.mouseY) + ")"
i.dl().text(txt, (10,i.height / 2), color=col)
txt = "color: " + str(i.getPixel(d.mouseX,d.mouseY))
i.dl().text(txt, (10,(i.height / 2) + 10), color=col)
print "coord: (" + str(d.mouseX) + "," + str(d.mouseY) + "), color: " + str(i.getPixel(d.mouseX,d.mouseY))
if elapsed_time > 0 and elapsed_time < 5:
i.dl().text("In live mode", (10,10), color=col)
i.dl().text("Left click will show mouse coordinates and color", (10,20), color=col)
i.dl().text("Right click will kill the live image", (10,30), color=col)
i.save(d)
if d.mouseRight:
print "Closing Window"
d.done = True
pg.quit()
def getColorSpace(self):
"""
**SUMMARY**
Returns the value matched in the color space class
**RETURNS**
Integer corresponding to the color space.
**EXAMPLE**
>>> if(image.getColorSpace() == ColorSpace.RGB)
**SEE ALSO**
:py:class:`ColorSpace`
"""
return self._colorSpace
def isRGB(self):
"""
**SUMMARY**
Returns true if this image uses the RGB colorspace.
**RETURNS**
True if the image uses the RGB colorspace, False otherwise.
**EXAMPLE**
>>> if( img.isRGB() ):
>>> r,g,b = img.splitChannels()
**SEE ALSO**
:py:meth:`toRGB`
"""
return(self._colorSpace==ColorSpace.RGB)
def isBGR(self):
"""
**SUMMARY**
Returns true if this image uses the BGR colorspace.
**RETURNS**
True if the image uses the BGR colorspace, False otherwise.
**EXAMPLE**
>>> if( img.isBGR() ):
>>> b,g,r = img.splitChannels()
**SEE ALSO**
:py:meth:`toBGR`
"""
return(self._colorSpace==ColorSpace.BGR)
def isHSV(self):
"""
**SUMMARY**
Returns true if this image uses the HSV colorspace.
**RETURNS**
True if the image uses the HSV colorspace, False otherwise.
**EXAMPLE**
>>> if( img.isHSV() ):
>>> h,s,v = img.splitChannels()
**SEE ALSO**
:py:meth:`toHSV`
"""
return(self._colorSpace==ColorSpace.HSV)
def isHLS(self):
"""
**SUMMARY**
Returns true if this image uses the HLS colorspace.
**RETURNS**
True if the image uses the HLS colorspace, False otherwise.
**EXAMPLE**
>>> if( img.isHLS() ):
>>> h,l,s = img.splitChannels()
**SEE ALSO**
:py:meth:`toHLS`
"""
return(self._colorSpace==ColorSpace.HLS)
def isXYZ(self):
"""
**SUMMARY**
Returns true if this image uses the XYZ colorspace.
**RETURNS**
True if the image uses the XYZ colorspace, False otherwise.
**EXAMPLE**
>>> if( img.isXYZ() ):
>>> x,y,z = img.splitChannels()
**SEE ALSO**
:py:meth:`toXYZ`
"""
return(self._colorSpace==ColorSpace.XYZ)
def isGray(self):
"""
**SUMMARY**
Returns true if this image uses the Gray colorspace.
**RETURNS**
True if the image uses the Gray colorspace, False otherwise.
**EXAMPLE**
>>> if( img.isGray() ):
>>> print "The image is in Grayscale."
**SEE ALSO**
:py:meth:`toGray`
"""
return(self._colorSpace==ColorSpace.GRAY)
def isYCrCb(self):
"""
**SUMMARY**
Returns true if this image uses the YCrCb colorspace.
**RETURNS**
True if the image uses the YCrCb colorspace, False otherwise.
**EXAMPLE**
>>> if( img.isYCrCb() ):
>>> Y,Cr,Cb = img.splitChannels()
**SEE ALSO**
:py:meth:`toYCrCb`
"""
return(self._colorSpace==ColorSpace.YCrCb)
def toRGB(self):
"""
**SUMMARY**
This method attemps to convert the image to the RGB colorspace.
If the color space is unknown we assume it is in the BGR format
**RETURNS**
Returns the converted image if the conversion was successful,
otherwise None is returned.
**EXAMPLE**
>>> img = Image("lenna")
>>> RGBImg = img.toRGB()
**SEE ALSO**
:py:meth:`isRGB`
"""
retVal = self.getEmpty()
if( self._colorSpace == ColorSpace.BGR or
self._colorSpace == ColorSpace.UNKNOWN ):
cv.CvtColor(self.getBitmap(), retVal, cv.CV_BGR2RGB)
elif( self._colorSpace == ColorSpace.HSV ):
cv.CvtColor(self.getBitmap(), retVal, cv.CV_HSV2RGB)
elif( self._colorSpace == ColorSpace.HLS ):
cv.CvtColor(self.getBitmap(), retVal, cv.CV_HLS2RGB)
elif( self._colorSpace == ColorSpace.XYZ ):
cv.CvtColor(self.getBitmap(), retVal, cv.CV_XYZ2RGB)
elif( self._colorSpace == ColorSpace.YCrCb ):
cv.CvtColor(self.getBitmap(), retVal, cv.CV_YCrCb2RGB)
elif( self._colorSpace == ColorSpace.RGB ):
retVal = self.getBitmap()
else:
logger.warning("Image.toRGB: There is no supported conversion to RGB colorspace")
return None
return Image(retVal, colorSpace=ColorSpace.RGB )
def toBGR(self):
"""
**SUMMARY**
This method attemps to convert the image to the BGR colorspace.
If the color space is unknown we assume it is in the BGR format.
**RETURNS**
Returns the converted image if the conversion was successful,
otherwise None is returned.
**EXAMPLE**
>>> img = Image("lenna")
>>> BGRImg = img.toBGR()
**SEE ALSO**
:py:meth:`isBGR`
"""
retVal = self.getEmpty()
if( self._colorSpace == ColorSpace.RGB or
self._colorSpace == ColorSpace.UNKNOWN ):
cv.CvtColor(self.getBitmap(), retVal, cv.CV_RGB2BGR)
elif( self._colorSpace == ColorSpace.HSV ):
cv.CvtColor(self.getBitmap(), retVal, cv.CV_HSV2BGR)
elif( self._colorSpace == ColorSpace.HLS ):
cv.CvtColor(self.getBitmap(), retVal, cv.CV_HLS2BGR)
elif( self._colorSpace == ColorSpace.XYZ ):
cv.CvtColor(self.getBitmap(), retVal, cv.CV_XYZ2BGR)
elif( self._colorSpace == ColorSpace.YCrCb ):
cv.CvtColor(self.getBitmap(), retVal, cv.CV_YCrCb2BGR)
elif( self._colorSpace == ColorSpace.BGR ):
retVal = self.getBitmap()
else:
logger.warning("Image.toBGR: There is no supported conversion to BGR colorspace")
return None
return Image(retVal, colorSpace = ColorSpace.BGR )
def toHLS(self):
"""
**SUMMARY**
This method attempts to convert the image to the HLS colorspace.
If the color space is unknown we assume it is in the BGR format.
**RETURNS**
Returns the converted image if the conversion was successful,
otherwise None is returned.
**EXAMPLE**
>>> img = Image("lenna")
>>> HLSImg = img.toHLS()
**SEE ALSO**
:py:meth:`isHLS`
"""
retVal = self.getEmpty()
if( self._colorSpace == ColorSpace.BGR or
self._colorSpace == ColorSpace.UNKNOWN ):
cv.CvtColor(self.getBitmap(), retVal, cv.CV_BGR2HLS)
elif( self._colorSpace == ColorSpace.RGB):
cv.CvtColor(self.getBitmap(), retVal, cv.CV_RGB2HLS)
elif( self._colorSpace == ColorSpace.HSV ):
cv.CvtColor(self.getBitmap(), retVal, cv.CV_HSV2RGB)
cv.CvtColor(retVal, retVal, cv.CV_RGB2HLS)
elif( self._colorSpace == ColorSpace.XYZ ):
cv.CvtColor(self.getBitmap(), retVal, cv.CV_XYZ2RGB)
cv.CvtColor(retVal, retVal, cv.CV_RGB2HLS)
elif( self._colorSpace == ColorSpace.YCrCb ):
cv.CvtColor(self.getBitmap(), retVal, cv.CV_YCrCb2RGB)
cv.CvtColor(retVal, retVal, cv.CV_RGB2HLS)
elif( self._colorSpace == ColorSpace.HLS ):
retVal = self.getBitmap()
else:
logger.warning("Image.toHSL: There is no supported conversion to HSL colorspace")
return None
return Image(retVal, colorSpace = ColorSpace.HLS )
def toHSV(self):
"""
**SUMMARY**
This method attempts to convert the image to the HSV colorspace.
If the color space is unknown we assume it is in the BGR format
**RETURNS**
Returns the converted image if the conversion was successful,
otherwise None is returned.
**EXAMPLE**
>>> img = Image("lenna")
>>> HSVImg = img.toHSV()
**SEE ALSO**
:py:meth:`isHSV`
"""
retVal = self.getEmpty()
if( self._colorSpace == ColorSpace.BGR or
self._colorSpace == ColorSpace.UNKNOWN ):
cv.CvtColor(self.getBitmap(), retVal, cv.CV_BGR2HSV)
elif( self._colorSpace == ColorSpace.RGB):
cv.CvtColor(self.getBitmap(), retVal, cv.CV_RGB2HSV)
elif( self._colorSpace == ColorSpace.HLS ):
cv.CvtColor(self.getBitmap(), retVal, cv.CV_HLS2RGB)
cv.CvtColor(retVal, retVal, cv.CV_RGB2HSV)
elif( self._colorSpace == ColorSpace.XYZ ):
cv.CvtColor(self.getBitmap(), retVal, cv.CV_XYZ2RGB)
cv.CvtColor(retVal, retVal, cv.CV_RGB2HSV)
elif( self._colorSpace == ColorSpace.YCrCb ):
cv.CvtColor(self.getBitmap(), retVal, cv.CV_YCrCb2RGB)
cv.CvtColor(retVal, retVal, cv.CV_RGB2HSV)
elif( self._colorSpace == ColorSpace.HSV ):
retVal = self.getBitmap()
else:
logger.warning("Image.toHSV: There is no supported conversion to HSV colorspace")
return None
return Image(retVal, colorSpace = ColorSpace.HSV )
def toXYZ(self):
"""
**SUMMARY**
This method attemps to convert the image to the XYZ colorspace.
If the color space is unknown we assume it is in the BGR format
**RETURNS**
Returns the converted image if the conversion was successful,
otherwise None is returned.
**EXAMPLE**
>>> img = Image("lenna")
>>> XYZImg = img.toXYZ()
**SEE ALSO**
:py:meth:`isXYZ`
"""
retVal = self.getEmpty()
if( self._colorSpace == ColorSpace.BGR or
self._colorSpace == ColorSpace.UNKNOWN ):
cv.CvtColor(self.getBitmap(), retVal, cv.CV_BGR2XYZ)
elif( self._colorSpace == ColorSpace.RGB):
cv.CvtColor(self.getBitmap(), retVal, cv.CV_RGB2XYZ)
elif( self._colorSpace == ColorSpace.HLS ):
cv.CvtColor(self.getBitmap(), retVal, cv.CV_HLS2RGB)
cv.CvtColor(retVal, retVal, cv.CV_RGB2XYZ)
elif( self._colorSpace == ColorSpace.HSV ):
cv.CvtColor(self.getBitmap(), retVal, cv.CV_HSV2RGB)
cv.CvtColor(retVal, retVal, cv.CV_RGB2XYZ)
elif( self._colorSpace == ColorSpace.YCrCb ):
cv.CvtColor(self.getBitmap(), retVal, cv.CV_YCrCb2RGB)
cv.CvtColor(retVal, retVal, cv.CV_RGB2XYZ)
elif( self._colorSpace == ColorSpace.XYZ ):
retVal = self.getBitmap()
else:
logger.warning("Image.toXYZ: There is no supported conversion to XYZ colorspace")
return None
return Image(retVal, colorSpace=ColorSpace.XYZ )
def toGray(self):
"""
**SUMMARY**
This method attemps to convert the image to the grayscale colorspace.
If the color space is unknown we assume it is in the BGR format.
**RETURNS**
A grayscale SimpleCV image if successful.
otherwise None is returned.
**EXAMPLE**
>>> img = Image("lenna")
>>> img.toGray().binarize().show()
**SEE ALSO**
:py:meth:`isGray`
:py:meth:`binarize`
"""
retVal = self.getEmpty(1)
if( self._colorSpace == ColorSpace.BGR or
self._colorSpace == ColorSpace.UNKNOWN ):
cv.CvtColor(self.getBitmap(), retVal, cv.CV_BGR2GRAY)
elif( self._colorSpace == ColorSpace.RGB):
cv.CvtColor(self.getBitmap(), retVal, cv.CV_RGB2GRAY)
elif( self._colorSpace == ColorSpace.HLS ):
cv.CvtColor(self.getBitmap(), retVal, cv.CV_HLS2RGB)
cv.CvtColor(retVal, retVal, cv.CV_RGB2GRAY)
elif( self._colorSpace == ColorSpace.HSV ):
cv.CvtColor(self.getBitmap(), retVal, cv.CV_HSV2RGB)
cv.CvtColor(retVal, retVal, cv.CV_RGB2GRAY)
elif( self._colorSpace == ColorSpace.XYZ ):
cv.CvtColor(self.getBitmap(), retVal, cv.CV_XYZ2RGB)
cv.CvtColor(retVal, retVal, cv.CV_RGB2GRAY)
elif( self._colorSpace == ColorSpace.YCrCb ):
cv.CvtColor(self.getBitmap(), retVal, cv.CV_YCrCb2RGB)
cv.CvtColor(retVal, retVal, cv.CV_RGB2GRAY)
elif( self._colorSpace == ColorSpace.GRAY ):
retVal = self.getBitmap()
else:
logger.warning("Image.toGray: There is no supported conversion to gray colorspace")
return None
return Image(retVal, colorSpace = ColorSpace.GRAY )
def toYCrCb(self):
"""
**SUMMARY**
This method attemps to convert the image to the YCrCb colorspace.
If the color space is unknown we assume it is in the BGR format
**RETURNS**
Returns the converted image if the conversion was successful,
otherwise None is returned.
**EXAMPLE**
>>> img = Image("lenna")
>>> RGBImg = img.toYCrCb()
**SEE ALSO**
:py:meth:`isYCrCb`
"""
retVal = self.getEmpty()
if( self._colorSpace == ColorSpace.BGR or
self._colorSpace == ColorSpace.UNKNOWN ):
cv.CvtColor(self.getBitmap(), retVal, cv.CV_BGR2YCrCb)
elif( self._colorSpace == ColorSpace.RGB ):
cv.CvtColor(self.getBitmap(), retVal, cv.CV_RGB2YCrCb)
elif( self._colorSpace == ColorSpace.HSV ):
cv.CvtColor(self.getBitmap(), retVal, cv.CV_HSV2RGB)
cv.CvtColor(retVal, retVal, cv.CV_RGB2YCrCb)
elif( self._colorSpace == ColorSpace.HLS ):
cv.CvtColor(self.getBitmap(), retVal, cv.CV_HLS2RGB)
cv.CvtColor(retVal, retVal, cv.CV_RGB2YCrCb)
elif( self._colorSpace == ColorSpace.XYZ ):
cv.CvtColor(self.getBitmap(), retVal, cv.CV_XYZ2RGB)
cv.CvtColor(retVal, retVal, cv.CV_RGB2YCrCb)
elif( self._colorSpace == ColorSpace.YCrCb ):
retVal = self.getBitmap()
else:
logger.warning("Image.toYCrCb: There is no supported conversion to YCrCb colorspace")
return None
return Image(retVal, colorSpace=ColorSpace.YCrCb )
def getEmpty(self, channels=3):
"""
**SUMMARY**
Create a new, empty OpenCV bitmap with the specified number of channels (default 3).
This method basically creates an empty copy of the image. This is handy for
interfacing with OpenCV functions directly.
**PARAMETERS**
* *channels* - The number of channels in the returned OpenCV image.
**RETURNS**
Returns an black OpenCV IplImage that matches the width, height, and color
depth of the source image.
**EXAMPLE**
>>> img = Image("lenna")
>>> rawImg = img.getEmpty()
>>> cv.SomeOpenCVFunc(img.getBitmap(),rawImg)
**SEE ALSO**
:py:meth:`getBitmap`
:py:meth:`getFPMatrix`
:py:meth:`getPIL`
:py:meth:`getNumpy`
:py:meth:`getGrayNumpy`
:py:meth:`getGrayscaleMatrix`
"""
bitmap = cv.CreateImage(self.size(), cv.IPL_DEPTH_8U, channels)
cv.SetZero(bitmap)
return bitmap
def getBitmap(self):
"""
**SUMMARY**
Retrieve the bitmap (iplImage) of the Image. This is useful if you want
to use functions from OpenCV with SimpleCV's image class
**RETURNS**
Returns black OpenCV IplImage from this image.
**EXAMPLE**
>>> img = Image("lenna")
>>> rawImg = img.getBitmap()
>>> rawOut = img.getEmpty()
>>> cv.SomeOpenCVFunc(rawImg,rawOut)
**SEE ALSO**
:py:meth:`getEmpty`
:py:meth:`getFPMatrix`
:py:meth:`getPIL`
:py:meth:`getNumpy`
:py:meth:`getGrayNumpy`
:py:meth:`getGrayscaleMatrix`
"""
if (self._bitmap):
return self._bitmap
elif (self._matrix):
self._bitmap = cv.GetImage(self._matrix)
return self._bitmap
def getMatrix(self):
"""
**SUMMARY**
Get the matrix (cvMat) version of the image, required for some OpenCV algorithms.
**RETURNS**
Returns the OpenCV CvMat version of this image.
**EXAMPLE**
>>> img = Image("lenna")
>>> rawImg = img.getMatrix()
>>> rawOut = img.getEmpty()
>>> cv.SomeOpenCVFunc(rawImg,rawOut)
**SEE ALSO**
:py:meth:`getEmpty`
:py:meth:`getBitmap`
:py:meth:`getFPMatrix`
:py:meth:`getPIL`
:py:meth:`getNumpy`
:py:meth:`getGrayNumpy`
:py:meth:`getGrayscaleMatrix`
"""
if (self._matrix):
return self._matrix
else:
self._matrix = cv.GetMat(self.getBitmap()) #convert the bitmap to a matrix
return self._matrix
def getFPMatrix(self):
"""
**SUMMARY**
Converts the standard int bitmap to a floating point bitmap.
This is handy for some OpenCV functions.
**RETURNS**
Returns the floating point OpenCV CvMat version of this image.
**EXAMPLE**
>>> img = Image("lenna")
>>> rawImg = img.getFPMatrix()
>>> rawOut = img.getEmpty()
>>> cv.SomeOpenCVFunc(rawImg,rawOut)
**SEE ALSO**
:py:meth:`getEmpty`
:py:meth:`getBitmap`
:py:meth:`getMatrix`
:py:meth:`getPIL`
:py:meth:`getNumpy`
:py:meth:`getGrayNumpy`
:py:meth:`getGrayscaleMatrix`
"""
retVal = cv.CreateImage((self.width,self.height), cv.IPL_DEPTH_32F, 3)
cv.Convert(self.getBitmap(),retVal)
return retVal
def getPIL(self):
"""
**SUMMARY**
Get a PIL Image object for use with the Python Image Library
This is handy for some PIL functions.
**RETURNS**
Returns the Python Imaging Library (PIL) version of this image.
**EXAMPLE**
>>> img = Image("lenna")
>>> rawImg = img.getPIL()
**SEE ALSO**
:py:meth:`getEmpty`
:py:meth:`getBitmap`
:py:meth:`getMatrix`
:py:meth:`getFPMatrix`
:py:meth:`getNumpy`
:py:meth:`getGrayNumpy`
:py:meth:`getGrayscaleMatrix`
"""
if (not PIL_ENABLED):
return None
if (not self._pil):
rgbbitmap = self.getEmpty()
cv.CvtColor(self.getBitmap(), rgbbitmap, cv.CV_BGR2RGB)
self._pil = pil.fromstring("RGB", self.size(), rgbbitmap.tostring())
return self._pil
def getGrayNumpy(self):
"""
**SUMMARY**
Return a grayscale Numpy array of the image.
**RETURNS**
Returns the image, converted first to grayscale and then converted to a 2D numpy array.
**EXAMPLE**
>>> img = Image("lenna")
>>> rawImg = img.getGrayNumpy()
**SEE ALSO**
:py:meth:`getEmpty`
:py:meth:`getBitmap`
:py:meth:`getMatrix`
:py:meth:`getPIL`
:py:meth:`getNumpy`
:py:meth:`getGrayNumpy`
:py:meth:`getGrayscaleMatrix`
"""
if( self._grayNumpy != "" ):
return self._grayNumpy
else:
self._grayNumpy = uint8(np.array(cv.GetMat(self._getGrayscaleBitmap())).transpose())
return self._grayNumpy
def getNumpy(self):
"""
**SUMMARY**
Get a Numpy array of the image in width x height x RGB dimensions
**RETURNS**
Returns the image, converted first to grayscale and then converted to a 3D numpy array.
**EXAMPLE**
>>> img = Image("lenna")
>>> rawImg = img.getNumpy()
**SEE ALSO**
:py:meth:`getEmpty`
:py:meth:`getBitmap`
:py:meth:`getMatrix`
:py:meth:`getPIL`
:py:meth:`getGrayNumpy`
:py:meth:`getGrayscaleMatrix`
"""
if self._numpy != "":
return self._numpy
self._numpy = np.array(self.getMatrix())[:, :, ::-1].transpose([1, 0, 2])
return self._numpy
def getNumpyCv2(self):
"""
**SUMMARY**
Get a Numpy array of the image in width x height x RGB dimensions compatible with OpenCV >= 2.3
**RETURNS**
Returns the 3D numpy array of the image compatible with OpenCV >= 2.3
**EXAMPLE**
>>> img = Image("lenna")
>>> rawImg = img.getNumpyCv2()
**SEE ALSO**
:py:meth:`getEmpty`
:py:meth:`getBitmap`
:py:meth:`getMatrix`
:py:meth:`getPIL`
:py:meth:`getGrayNumpy`
:py:meth:`getGrayscaleMatrix`
:py:meth:`getNumpy`
:py:meth:`getGrayNumpyCv2`
"""
if type(self._cv2Numpy) is not np.ndarray:
self._cv2Numpy = np.array(self.getMatrix())
return self._cv2Numpy
def getGrayNumpyCv2(self):
"""
**SUMMARY**
Get a Grayscale Numpy array of the image in width x height y compatible with OpenCV >= 2.3
**RETURNS**
Returns the grayscale numpy array compatible with OpenCV >= 2.3
**EXAMPLE**
>>> img = Image("lenna")
>>> rawImg = img.getNumpyCv2()
**SEE ALSO**
:py:meth:`getEmpty`
:py:meth:`getBitmap`
:py:meth:`getMatrix`
:py:meth:`getPIL`
:py:meth:`getGrayNumpy`
:py:meth:`getGrayscaleMatrix`
:py:meth:`getNumpy`
:py:meth:`getGrayNumpyCv2`
"""
if not type(self._cv2GrayNumpy) is not np.ndarray:
self._cv2GrayNumpy = np.array(self.getGrayscaleMatrix())
return self._cv2GrayNumpy
def _getGrayscaleBitmap(self):
if (self._graybitmap):
return self._graybitmap
self._graybitmap = self.getEmpty(1)
temp = self.getEmpty(3)
if( self._colorSpace == ColorSpace.BGR or
self._colorSpace == ColorSpace.UNKNOWN ):
cv.CvtColor(self.getBitmap(), self._graybitmap, cv.CV_BGR2GRAY)
elif( self._colorSpace == ColorSpace.RGB):
cv.CvtColor(self.getBitmap(), self._graybitmap, cv.CV_RGB2GRAY)
elif( self._colorSpace == ColorSpace.HLS ):
cv.CvtColor(self.getBitmap(), temp, cv.CV_HLS2RGB)
cv.CvtColor(temp, self._graybitmap, cv.CV_RGB2GRAY)
elif( self._colorSpace == ColorSpace.HSV ):
cv.CvtColor(self.getBitmap(), temp, cv.CV_HSV2RGB)
cv.CvtColor(temp, self._graybitmap, cv.CV_RGB2GRAY)
elif( self._colorSpace == ColorSpace.XYZ ):
cv.CvtColor(self.getBitmap(), retVal, cv.CV_XYZ2RGB)
cv.CvtColor(temp, self._graybitmap, cv.CV_RGB2GRAY)
elif( self._colorSpace == ColorSpace.GRAY):
cv.Split(self.getBitmap(), self._graybitmap, self._graybitmap, self._graybitmap, None)
else:
logger.warning("Image._getGrayscaleBitmap: There is no supported conversion to gray colorspace")
return None
return self._graybitmap
def getGrayscaleMatrix(self):
"""
**SUMMARY**
Get the grayscale matrix (cvMat) version of the image, required for some OpenCV algorithms.
**RETURNS**
Returns the OpenCV CvMat version of this image.
**EXAMPLE**
>>> img = Image("lenna")
>>> rawImg = img.getGrayscaleMatrix()
>>> rawOut = img.getEmpty()
>>> cv.SomeOpenCVFunc(rawImg,rawOut)
**SEE ALSO**
:py:meth:`getEmpty`
:py:meth:`getBitmap`
:py:meth:`getFPMatrix`
:py:meth:`getPIL`
:py:meth:`getNumpy`
:py:meth:`getGrayNumpy`
:py:meth:`getMatrix`
"""
if (self._grayMatrix):
return self._grayMatrix
else:
self._grayMatrix = cv.GetMat(self._getGrayscaleBitmap()) #convert the bitmap to a matrix
return self._grayMatrix
def _getEqualizedGrayscaleBitmap(self):
if (self._equalizedgraybitmap):
return self._equalizedgraybitmap
self._equalizedgraybitmap = self.getEmpty(1)
cv.EqualizeHist(self._getGrayscaleBitmap(), self._equalizedgraybitmap)
return self._equalizedgraybitmap
def equalize(self):
"""
**SUMMARY**
Perform a histogram equalization on the image.
**RETURNS**
Returns a grayscale SimpleCV image.
**EXAMPLE**
>>> img = Image("lenna")
>>> img = img.equalize()
"""
return Image(self._getEqualizedGrayscaleBitmap())
def getPGSurface(self):
"""
**SUMMARY**
Returns the image as a pygame surface. This is used for rendering the display
**RETURNS**
A pygame surface object used for rendering.
"""
if (self._pgsurface):
return self._pgsurface
else:
if self.isGray():
self._pgsurface = pg.image.fromstring(self.getBitmap().tostring(), self.size(), "RGB")
else:
self._pgsurface = pg.image.fromstring(self.toRGB().getBitmap().tostring(), self.size(), "RGB")
return self._pgsurface
def toString(self):
"""
**SUMMARY**
Returns the image as a string, useful for moving data around.
**RETURNS**
The image, converted to rgb, then converted to a string.
"""
return self.toRGB().getBitmap().tostring()
def save(self, filehandle_or_filename="", mode="", verbose=False, temp=False, path=None, fname=None, **params):
"""
**SUMMARY**
Save the image to the specified filename. If no filename is provided then
then it will use the filename the Image was loaded from or the last
place it was saved to. You can save to lots of places, not just files.
For example you can save to the Display, a JpegStream, VideoStream,
temporary file, or Ipython Notebook.
Save will implicitly render the image's layers before saving, but the layers are
not applied to the Image itself.
**PARAMETERS**
* *filehandle_or_filename* - the filename to which to store the file. The method will infer the file type.
* *mode* - This flag is used for saving using pul.
* *verbose* - If this flag is true we return the path where we saved the file.
* *temp* - If temp is True we save the image as a temporary file and return the path
* *path* - path where temporary files needed to be stored
* *fname* - name(Prefix) of the temporary file.
* *params* - This object is used for overloading the PIL save methods. In particular
this method is useful for setting the jpeg compression level. For JPG see this documentation:
http://www.pythonware.com/library/pil/handbook/format-jpeg.htm
**EXAMPLES**
To save as a temporary file just use:
>>> img = Image('simplecv')
>>> img.save(temp=True)
It will return the path that it saved to.
Save also supports IPython Notebooks when passing it a Display object
that has been instainted with the notebook flag.
To do this just use:
>>> disp = Display(displaytype='notebook')
>>> img.save(disp)
.. Note::
You must have IPython notebooks installed for this to work
path and fname are valid if and only if temp is set to True.
.. attention::
We need examples for all save methods as they are unintuitve.
"""
#TODO, we use the term mode here when we mean format
#TODO, if any params are passed, use PIL
if temp and path!=None :
import glob
if fname==None :
fname = 'Image'
if glob.os.path.exists(path):
path = glob.os.path.abspath(path)
imagefiles = glob.glob(glob.os.path.join(path,fname+"*.png"))
num = [0]
for img in imagefiles :
num.append(int(glob.re.findall('[0-9]+$',img[:-4])[-1]))
num.sort()
fnum = num[-1]+1
fname = glob.os.path.join(path,fname+str(fnum)+".png")
self._tempFiles.append(fname)
self.save(self._tempFiles[-1])
return self._tempFiles[-1]
else :
print "Path does not exist!"
#if it's a temporary file
elif temp :
self._tempFiles.append(tempfile.NamedTemporaryFile(suffix=".png"))
self.save(self._tempFiles[-1].name)
return self._tempFiles[-1].name
if (not filehandle_or_filename):
if (self.filename):
filehandle_or_filename = self.filename
else:
filehandle_or_filename = self.filehandle
if (len(self._mLayers)):
saveimg = self.applyLayers()
else:
saveimg = self
if self._colorSpace != ColorSpace.BGR and self._colorSpace != ColorSpace.GRAY:
saveimg = saveimg.toBGR()
if not isinstance(filehandle_or_filename, basestring):
fh = filehandle_or_filename
if (not PIL_ENABLED):
logger.warning("You need the python image library to save by filehandle")
return 0
if (type(fh) == InstanceType and fh.__class__.__name__ == "JpegStreamer"):
fh.jpgdata = StringIO()
saveimg.getPIL().save(fh.jpgdata, "jpeg", **params) #save via PIL to a StringIO handle
fh.refreshtime = time.time()
self.filename = ""
self.filehandle = fh
elif (type(fh) == InstanceType and fh.__class__.__name__ == "VideoStream"):
self.filename = ""
self.filehandle = fh
fh.writeFrame(saveimg)
elif (type(fh) == InstanceType and fh.__class__.__name__ == "Display"):
if fh.displaytype == 'notebook':
try:
from IPython.core.display import Image as IPImage
except ImportError:
print "You need IPython Notebooks to use this display mode"
return
from IPython.core import display as Idisplay
tf = tempfile.NamedTemporaryFile(suffix=".png")
loc = '/tmp/' + tf.name.split('/')[-1]
tf.close()
self.save(loc)
Idisplay.display(IPImage(filename=loc))
return
else:
self.filename = ""
self.filehandle = fh
fh.writeFrame(saveimg)
else:
if (not mode):
mode = "jpeg"
saveimg.getPIL().save(fh, mode, **params)
self.filehandle = fh #set the filename for future save operations
self.filename = ""
if verbose:
print self.filename
return 1
#make a temporary file location if there isn't one
if not filehandle_or_filename:
filename = tempfile.mkstemp(suffix=".png")[-1]
else:
filename = filehandle_or_filename
#allow saving in webp format
if re.search('\.webp$', filename):
try:
#newer versions of PIL support webp format, try that first
self.getPIL().save(filename, **params)
except:
#if PIL doesn't support it, maybe we have the python-webm library
try:
from webm import encode as webmEncode
from webm.handlers import BitmapHandler, WebPHandler
except:
logger.warning('You need the webm library to save to webp format. You can download from: https://github.com/ingenuitas/python-webm')
return 0
#PNG_BITMAP_DATA = bytearray(Image.open(PNG_IMAGE_FILE).tostring())
PNG_BITMAP_DATA = bytearray(self.toString())
IMAGE_WIDTH = self.width
IMAGE_HEIGHT = self.height
image = BitmapHandler(
PNG_BITMAP_DATA, BitmapHandler.RGB,
IMAGE_WIDTH, IMAGE_HEIGHT, IMAGE_WIDTH * 3
)
result = webmEncode.EncodeRGB(image)
file(filename.format("RGB"), "wb").write(result.data)
return 1
#if the user is passing kwargs use the PIL save method.
if( params ): #usually this is just the compression rate for the image
if (not mode):
mode = "jpeg"
saveimg.getPIL().save(filename, mode, **params)
return 1
if (filename):
cv.SaveImage(filename, saveimg.getBitmap())
self.filename = filename #set the filename for future save operations
self.filehandle = ""
elif (self.filename):
cv.SaveImage(self.filename, saveimg.getBitmap())
else:
return 0
if verbose:
print self.filename
if temp:
return filename
else:
return 1
def copy(self):
"""
**SUMMARY**
Return a full copy of the Image's bitmap. Note that this is different
from using python's implicit copy function in that only the bitmap itself
is copied. This method essentially performs a deep copy.
**RETURNS**
A copy of this SimpleCV image.
**EXAMPLE**
>>> img = Image("logo")
>>> img2 = img.copy()
"""
newimg = self.getEmpty()
cv.Copy(self.getBitmap(), newimg)
return Image(newimg, colorSpace=self._colorSpace)
def upload(self,dest,api_key=None,api_secret=None, verbose = True):
"""
**SUMMARY**
Uploads image to imgur or flickr. In verbose mode URL values are printed.
**PARAMETERS**
* *api_key* - a string of the API key.
* *api_secret* (required only for flickr) - a string of the API secret.
* *verbose* - If verbose is true all values are printed to the
screen
**RETURNS**
if uploading is successful,
- Imgur return the original image URL on success and None if it fails.
- Flick returns True on success, else returns False.
**EXAMPLE**
TO upload image to imgur
>>> img = Image("lenna")
>>> result = img.upload( 'imgur',"MY_API_KEY1234567890" )
>>> print "Uploaded To: " + result[0]
To upload image to flickr
>>> img.upload('flickr','api_key','api_secret')
>>> img.invert().upload('flickr') #Once the api keys and secret keys are cached.
**NOTES**
.. Warning::
This method requires two packages to be installed
-PyCurl
-flickr api.
.. Warning::
You must supply your own API key. See here:
- http://imgur.com/register/api_anon
- http://www.flickr.com/services/api/misc.api_keys.html
"""
if ( dest=='imgur' ) :
try:
import pycurl
except ImportError:
print "PycURL Library not installed."
return
response = StringIO()
c = pycurl.Curl()
values = [("key", api_key),
("image", (c.FORM_FILE, self.filename))]
c.setopt(c.URL, "http://api.imgur.com/2/upload.xml")
c.setopt(c.HTTPPOST, values)
c.setopt(c.WRITEFUNCTION, response.write)
c.perform()
c.close()
match = re.search(r'<hash>(\w+).*?<deletehash>(\w+).*?<original>(http://[\w.]+/[\w.]+)', response.getvalue() , re.DOTALL)
if match:
if(verbose):
print "Imgur page: http://imgur.com/" + match.group(1)
print "Original image: " + match.group(3)
print "Delete page: http://imgur.com/delete/" + match.group(2)
return [match.group(1),match.group(3),match.group(2)]
else :
if(verbose):
print "The API Key given is not valid"
return None
elif (dest=='flickr'):
global temp_token
flickr = None
try :
import flickrapi
except ImportError:
print "Flickr API is not installed. Please install it from http://pypi.python.org/pypi/flickrapi"
return False
try :
if (not(api_key==None and api_secret==None)):
self.flickr = flickrapi.FlickrAPI(api_key,api_secret,cache=True)
self.flickr.cache = flickrapi.SimpleCache(timeout=3600, max_entries=200)
self.flickr.authenticate_console('write')
temp_token = (api_key,api_secret)
else :
try :
self.flickr = flickrapi.FlickrAPI(temp_token[0],temp_token[1],cache=True)
self.flickr.authenticate_console('write')
except NameError :
print "API key and Secret key are not set."
return
except :
print "The API Key and Secret Key are not valid"
return False
if (self.filename) :
try :
self.flickr.upload(self.filename,self.filehandle)
except :
print "Uploading Failed !"
return False
else :
import tempfile
tf=tempfile.NamedTemporaryFile(suffix='.jpg')
self.save(tf.name)
temp = Image(tf.name)
self.flickr.upload(tf.name,temp.filehandle)
return True
def scale(self, width, height = -1):
"""
**SUMMARY**
Scale the image to a new width and height.
If no height is provided, the width is considered a scaling value.
**PARAMETERS**
* *width* - either the new width in pixels, if the height parameter is > 0, or if this value
is a floating point value, this is the scaling factor.
* *height* - the new height in pixels.
**RETURNS**
The resized image.
**EXAMPLE**
>>> img.scale(200, 100) #scales the image to 200px x 100px
>>> img.scale(2.0) #enlarges the image to 2x its current size
.. Warning::
The two value scale command is deprecated. To set width and height
use the resize function.
:py:meth:`resize`
"""
w, h = width, height
if height == -1:
w = int(self.width * width)
h = int(self.height * width)
if( w > MAX_DIMENSION or h > MAX_DIMENSION or h < 1 or w < 1 ):
logger.warning("Holy Heck! You tried to make an image really big or impossibly small. I can't scale that")
return self
scaled_bitmap = cv.CreateImage((w, h), 8, 3)
cv.Resize(self.getBitmap(), scaled_bitmap)
return Image(scaled_bitmap, colorSpace=self._colorSpace)
def resize(self, w=None,h=None):
"""
**SUMMARY**
This method resizes an image based on a width, a height, or both.
If either width or height is not provided the value is inferred by keeping the aspect ratio.
If both values are provided then the image is resized accordingly.
**PARAMETERS**
* *width* - The width of the output image in pixels.
* *height* - The height of the output image in pixels.
**RETURNS**
Returns a resized image, if the size is invalid a warning is issued and
None is returned.
**EXAMPLE**
>>> img = Image("lenna")
>>> img2 = img.resize(w=1024) # h is guessed from w
>>> img3 = img.resize(h=1024) # w is guessed from h
>>> img4 = img.resize(w=200,h=100)
"""
retVal = None
if( w is None and h is None ):
logger.warning("Image.resize has no parameters. No operation is performed")
return None
elif( w is not None and h is None):
sfactor = float(w)/float(self.width)
h = int( sfactor*float(self.height) )
elif( w is None and h is not None):
sfactor = float(h)/float(self.height)
w = int( sfactor*float(self.width) )
if( w > MAX_DIMENSION or h > MAX_DIMENSION ):
logger.warning("Image.resize Holy Heck! You tried to make an image really big or impossibly small. I can't scale that")
return retVal
scaled_bitmap = cv.CreateImage((w, h), 8, 3)
cv.Resize(self.getBitmap(), scaled_bitmap)
return Image(scaled_bitmap, colorSpace=self._colorSpace)
def smooth(self, algorithm_name='gaussian', aperture=(3,3), sigma=0, spatial_sigma=0, grayscale=False, aperature=None):
"""
**SUMMARY**
Smooth the image, by default with the Gaussian blur. If desired,
additional algorithms and apertures can be specified. Optional parameters
are passed directly to OpenCV's cv.Smooth() function.
If grayscale is true the smoothing operation is only performed on a single channel
otherwise the operation is performed on each channel of the image.
for OpenCV versions >= 2.3.0 it is advisible to take a look at
- :py:meth:`bilateralFilter`
- :py:meth:`medianFilter`
- :py:meth:`blur`
- :py:meth:`gaussianBlur`
**PARAMETERS**
* *algorithm_name* - valid options are 'blur' or gaussian, 'bilateral', and 'median'.
* `Median Filter <http://en.wikipedia.org/wiki/Median_filter>`_
* `Gaussian Blur <http://en.wikipedia.org/wiki/Gaussian_blur>`_
* `Bilateral Filter <http://en.wikipedia.org/wiki/Bilateral_filter>`_
* *aperture* - A tuple for the aperture of the gaussian blur as an (x,y) tuple.
- Note there was rampant spelling mistakes in both smooth & sobel,
aperture is spelled as such, and not "aperature". This code is backwards
compatible.
.. Warning::
These must be odd numbers.
* *sigma* -
* *spatial_sigma* -
* *grayscale* - Return just the grayscale image.
**RETURNS**
The smoothed image.
**EXAMPLE**
>>> img = Image("Lenna")
>>> img2 = img.smooth()
>>> img3 = img.smooth('median')
**SEE ALSO**
:py:meth:`bilateralFilter`
:py:meth:`medianFilter`
:py:meth:`blur`
"""
# see comment on argument documentation (spelling error)
aperture = aperature if aperature else aperture
if is_tuple(aperture):
win_x, win_y = aperture
if win_x <= 0 or win_y <= 0 or win_x % 2 == 0 or win_y % 2 == 0:
logger.warning("The aperture (x,y) must be odd number and greater than 0.")
return None
else:
raise ValueError("Please provide a tuple to aperture, got: %s" % type(aperture))
#gauss and blur can work in-place, others need a buffer frame
#use a string to ID rather than the openCV constant
if algorithm_name == "blur":
algorithm = cv.CV_BLUR
elif algorithm_name == "bilateral":
algorithm = cv.CV_BILATERAL
win_y = win_x #aperture must be square
elif algorithm_name == "median":
algorithm = cv.CV_MEDIAN
win_y = win_x #aperture must be square
else:
algorithm = cv.CV_GAUSSIAN #default algorithm is gaussian
if grayscale:
newimg = self.getEmpty(1)
cv.Smooth(self._getGrayscaleBitmap(), newimg, algorithm, win_x, win_y, sigma, spatial_sigma)
else:
newimg = self.getEmpty(3)
r = self.getEmpty(1)
g = self.getEmpty(1)
b = self.getEmpty(1)
ro = self.getEmpty(1)
go = self.getEmpty(1)
bo = self.getEmpty(1)
cv.Split(self.getBitmap(), b, g, r, None)
cv.Smooth(r, ro, algorithm, win_x, win_y, sigma, spatial_sigma)
cv.Smooth(g, go, algorithm, win_x, win_y, sigma, spatial_sigma)
cv.Smooth(b, bo, algorithm, win_x, win_y, sigma, spatial_sigma)
cv.Merge(bo,go,ro, None, newimg)
return Image(newimg, colorSpace=self._colorSpace)
def medianFilter(self, window='',grayscale=False):
"""
**SUMMARY**
Smooths the image, with the median filter. Performs a median filtering operation to denoise/despeckle the image.
The optional parameter is the window size.
see : http://en.wikipedia.org/wiki/Median_filter
**Parameters**
* *window* - should be in the form a tuple (win_x,win_y). Where win_x should be equal to win_y.
- By default it is set to 3x3, i.e window = (3x3).
**Note**
win_x and win_y should be greater than zero, a odd number and equal.
For OpenCV versions <= 2.3.0
-- this acts as Convience function derived from the :py:meth:`smooth` method. Which internally calls cv.Smooth
For OpenCV versions >= 2.3.0
-- cv2.medianBlur function is called.
"""
try:
import cv2
new_version = True
except :
new_version = False
pass
if is_tuple(window):
win_x, win_y = window
if ( win_x>=0 and win_y>=0 and win_x%2==1 and win_y%2==1 ) :
if win_x != win_y :
win_x=win_y
else :
logger.warning("The aperture (win_x,win_y) must be odd number and greater than 0.")
return None
elif( is_number(window) ):
win_x = window
else :
win_x = 3 #set the default aperture window size (3x3)
if ( not new_version ) :
grayscale_ = grayscale
return self.smooth(algorithm_name='median', aperture=(win_x,win_y),grayscale=grayscale_)
else :
if (grayscale) :
img_medianBlur = cv2.medianBlur(self.getGrayNumpy(),win_x)
return Image(img_medianBlur, colorSpace=ColorSpace.GRAY)
else :
img_medianBlur = cv2.medianBlur(self.getNumpy()[:,:, ::-1].transpose([1,0,2]),win_x)
img_medianBlur = img_medianBlur[:,:, ::-1].transpose([1,0,2])
return Image(img_medianBlur, colorSpace=self._colorSpace)
def bilateralFilter(self, diameter=5,sigmaColor=10, sigmaSpace=10,grayscale=False):
"""
**SUMMARY**
Smooths the image, using bilateral filtering. Potential of bilateral filtering is for the removal of texture.
The optional parameter are diameter, sigmaColor, sigmaSpace.
Bilateral Filter
see : http://en.wikipedia.org/wiki/Bilateral_filter
see : http://homepages.inf.ed.ac.uk/rbf/CVonline/LOCAL_COPIES/MANDUCHI1/Bilateral_Filtering.html
**Parameters**
* *diameter* - A tuple for the window of the form (diameter,diameter). By default window = (3x3). ( for OpenCV versions <= 2.3.0)
- Diameter of each pixel neighborhood that is used during filtering. ( for OpenCV versions >= 2.3.0)
* *sigmaColor* - Filter the specified value in the color space. A larger value of the parameter means that farther colors within the pixel neighborhood (see sigmaSpace ) will be mixed together, resulting in larger areas of semi-equal color.
* *sigmaSpace* - Filter the specified value in the coordinate space. A larger value of the parameter means that farther pixels will influence each other as long as their colors are close enough
**NOTE**
For OpenCV versions <= 2.3.0
-- this acts as Convience function derived from the :py:meth:`smooth` method. Which internally calls cv.Smooth.
-- where aperture(window) is (diameter,diameter)
-- sigmaColor and sigmanSpace become obsolete
For OpenCV versions higher than 2.3.0. i.e >= 2.3.0
-- cv.bilateralFilter function is called
-- If the sigmaColor and sigmaSpace values are small (< 10), the filter will not have much effect, whereas if they are large (> 150), they will have a very strong effect, making the image look 'cartoonish'
-- It is recommended to use diamter=5 for real time applications, and perhaps diameter=9 for offile applications that needs heavy noise filtering.
"""
try:
import cv2
new_version = True
except :
new_version = False
pass
if is_tuple(diameter):
win_x, win_y = diameter
if ( win_x>=0 and win_y>=0 and win_x%2==1 and win_y%2==1 ) :
if win_x != win_y :
diameter = (win_x, win_y)
else :
logger.warning("The aperture (win_x,win_y) must be odd number and greater than 0.")
return None
elif( is_number(diameter) ):
pass
else :
win_x = 3 #set the default aperture window size (3x3)
diameter = (win_x,win_x)
if ( not new_version ) :
grayscale_ = grayscale
if( is_number(diameter) ) :
diameter = (diameter,diameter)
return self.smooth(algorithm_name='bilateral', aperture=diameter,grayscale=grayscale_)
else :
if (grayscale) :
img_bilateral = cv2.bilateralFilter(self.getGrayNumpy(),diameter,sigmaColor, sigmaSpace)
return Image(img_bilateral, colorSpace=ColorSpace.GRAY)
else :
img_bilateral = cv2.bilateralFilter(self.getNumpy()[:,:, ::-1].transpose([1,0,2]),diameter,sigmaColor, sigmaSpace)
img_bilateral = img_bilateral[:,:, ::-1].transpose([1,0,2])
return Image(img_bilateral,colorSpace=self._colorSpace)
def blur(self, window = '', grayscale=False):
"""
**SUMMARY**
Smoothes an image using the normalized box filter.
The optional parameter is window.
see : http://en.wikipedia.org/wiki/Blur
**Parameters**
* *window* - should be in the form a tuple (win_x,win_y).
- By default it is set to 3x3, i.e window = (3x3).
**NOTE**
For OpenCV versions <= 2.3.0
-- this acts as Convience function derived from the :py:meth:`smooth` method. Which internally calls cv.Smooth
For OpenCV versions higher than 2.3.0. i.e >= 2.3.0
-- cv.blur function is called
"""
try:
import cv2
new_version = True
except :
new_version = False
pass
if is_tuple(window):
win_x, win_y = window
if ( win_x<=0 or win_y<=0 ) :
logger.warning("win_x and win_y should be greater than 0.")
return None
elif( is_number(window) ):
window = (window,window)
else :
window = (3,3)
if ( not new_version ) :
grayscale_ = grayscale
return self.smooth(algorithm_name='blur', aperture=window, grayscale=grayscale_)
else :
if grayscale:
img_blur = cv2.blur(self.getGrayNumpy(),window)
return Image(img_blur,colorSpace=ColorSpace.GRAY)
else :
img_blur = cv2.blur(self.getNumpy()[:,:, ::-1].transpose([1,0,2]),window)
img_blur = img_blur[:,:, ::-1].transpose([1,0,2])
return Image(img_blur,colorSpace=self._colorSpace)
def gaussianBlur(self, window = '', sigmaX=0 , sigmaY=0 ,grayscale=False):
"""
**SUMMARY**
Smoothes an image, typically used to reduce image noise and reduce detail.
The optional parameter is window.
see : http://en.wikipedia.org/wiki/Gaussian_blur
**Parameters**
* *window* - should be in the form a tuple (win_x,win_y). Where win_x and win_y should be positive and odd.
- By default it is set to 3x3, i.e window = (3x3).
* *sigmaX* - Gaussian kernel standard deviation in X direction.
* *sigmaY* - Gaussian kernel standard deviation in Y direction.
* *grayscale* - If true, the effect is applied on grayscale images.
**NOTE**
For OpenCV versions <= 2.3.0
-- this acts as Convience function derived from the :py:meth:`smooth` method. Which internally calls cv.Smooth
For OpenCV versions higher than 2.3.0. i.e >= 2.3.0
-- cv.GaussianBlur function is called
"""
try:
import cv2
ver = cv2.__version__
new_version = False
#For OpenCV versions till 2.4.0, cv2.__versions__ are of the form "$Rev: 4557 $"
if not ver.startswith('$Rev:'):
if int(ver.replace('.','0'))>=20300 :
new_version = True
except :
new_version = False
pass
if is_tuple(window):
win_x, win_y = window
if ( win_x>=0 and win_y>=0 and win_x%2==1 and win_y%2==1 ) :
pass
else :
logger.warning("The aperture (win_x,win_y) must be odd number and greater than 0.")
return None
elif( is_number(window) ):
window = (window, window)
else :
window = (3,3) #set the default aperture window size (3x3)
if ( not new_version ) :
grayscale_ = grayscale
return self.smooth(algorithm_name='blur', aperture=window, grayscale=grayscale_)
else :
if grayscale :
img_guass = self.getGrayNumpy()
cv2.GaussianBlur(self.getGrayNumpy(),window,sigmaX,img_guass,sigmaY)
return Image(img_guass, colorSpace=ColorSpace.GRAY)
else :
img_guass = self.getNumpy()[:,:, ::-1].transpose([1,0,2])
cv2.GaussianBlur(self.getNumpy()[:,:, ::-1].transpose([1,0,2]),window,sigmaX,img_guass,sigmaY)
img_guass = img_guass[:,:, ::-1].transpose([1,0,2])
return Image(img_guass,colorSpace=self._colorSpace)
def invert(self):
"""
**SUMMARY**
Invert (negative) the image note that this can also be done with the
unary minus (-) operator. For binary image this turns black into white and white into black (i.e. white is the new black).
**RETURNS**
The opposite of the current image.
**EXAMPLE**
>>> img = Image("polar_bear_in_the_snow.png")
>>> img.invert().save("black_bear_at_night.png")
**SEE ALSO**
:py:meth:`binarize`
"""
return -self
def grayscale(self):
"""
**SUMMARY**
This method returns a gray scale version of the image. It makes everything look like an old movie.
**RETURNS**
A grayscale SimpleCV image.
**EXAMPLE**
>>> img = Image("lenna")
>>> img.grayscale().binarize().show()
**SEE ALSO**
:py:meth:`binarize`
"""
return Image(self._getGrayscaleBitmap(), colorSpace = ColorSpace.GRAY)
def flipHorizontal(self):
"""
**SUMMARY**
Horizontally mirror an image.
.. Warning::
Note that flip does not mean rotate 180 degrees! The two are different.
**RETURNS**
The flipped SimpleCV image.
**EXAMPLE**
>>> img = Image("lenna")
>>> upsidedown = img.flipHorizontal()
**SEE ALSO**
:py:meth:`flipVertical`
:py:meth:`rotate`
"""
newimg = self.getEmpty()
cv.Flip(self.getBitmap(), newimg, 1)
return Image(newimg, colorSpace=self._colorSpace)
def flipVertical(self):
"""
**SUMMARY**
Vertically mirror an image.
.. Warning::
Note that flip does not mean rotate 180 degrees! The two are different.
**RETURNS**
The flipped SimpleCV image.
**EXAMPLE**
>>> img = Image("lenna")
>>> upsidedown = img.flipHorizontal()
**SEE ALSO**
:py:meth:`rotate`
:py:meth:`flipHorizontal`
"""
newimg = self.getEmpty()
cv.Flip(self.getBitmap(), newimg, 0)
return Image(newimg, colorSpace=self._colorSpace)
def stretch(self, thresh_low = 0, thresh_high = 255):
"""
**SUMMARY**
The stretch filter works on a greyscale image, if the image
is color, it returns a greyscale image. The filter works by
taking in a lower and upper threshold. Anything below the lower
threshold is pushed to black (0) and anything above the upper
threshold is pushed to white (255)
**PARAMETERS**
* *thresh_low* - The lower threshold for the stretch operation.
This should be a value between 0 and 255.
* *thresh_high* - The upper threshold for the stretch operation.
This should be a value between 0 and 255.
**RETURNS**
A gray scale version of the image with the appropriate histogram stretching.
**EXAMPLE**
>>> img = Image("orson_welles.jpg")
>>> img2 = img.stretch(56.200)
>>> img2.show()
**NOTES**
TODO - make this work on RGB images with thresholds for each channel.
**SEE ALSO**
:py:meth:`binarize`
:py:meth:`equalize`
"""
try:
newimg = self.getEmpty(1)
cv.Threshold(self._getGrayscaleBitmap(), newimg, thresh_low, 255, cv.CV_THRESH_TOZERO)
cv.Not(newimg, newimg)
cv.Threshold(newimg, newimg, 255 - thresh_high, 255, cv.CV_THRESH_TOZERO)
cv.Not(newimg, newimg)
return Image(newimg)
except:
return None
def binarize(self, thresh = -1, maxv = 255, blocksize = 0, p = 5):
"""
**SUMMARY**
Do a binary threshold the image, changing all values below thresh to maxv
and all above to black. If a color tuple is provided, each color channel
is thresholded separately.
If threshold is -1 (default), an adaptive method (OTSU's method) is used.
If then a blocksize is specified, a moving average over each region of block*block
pixels a threshold is applied where threshold = local_mean - p.
**PARAMETERS**
* *thresh* - the threshold as an integer or an (r,g,b) tuple , where pixels below (darker) than thresh are set to to max value,
and all values above this value are set to black. If this parameter is -1 we use Otsu's method.
* *maxv* - The maximum value for pixels below the threshold. Ordinarily this should be 255 (white)
* *blocksize* - the size of the block used in the adaptive binarize operation.
.. Warning::
This parameter must be an odd number.
* *p* - The difference from the local mean to use for thresholding in Otsu's method.
**RETURNS**
A binary (two colors, usually black and white) SimpleCV image. This works great for the findBlobs
family of functions.
**EXAMPLE**
Example of a vanila threshold versus an adaptive threshold:
>>> img = Image("orson_welles.jpg")
>>> b1 = img.binarize(128)
>>> b2 = img.binarize(blocksize=11,p=7)
>>> b3 = b1.sideBySide(b2)
>>> b3.show()
**NOTES**
`Otsu's Method Description<http://en.wikipedia.org/wiki/Otsu's_method>`
**SEE ALSO**
:py:meth:`threshold`
:py:meth:`findBlobs`
:py:meth:`invert`
:py:meth:`dilate`
:py:meth:`erode`
"""
if is_tuple(thresh):
r = self.getEmpty(1)
g = self.getEmpty(1)
b = self.getEmpty(1)
cv.Split(self.getBitmap(), b, g, r, None)
cv.Threshold(r, r, thresh[0], maxv, cv.CV_THRESH_BINARY_INV)
cv.Threshold(g, g, thresh[1], maxv, cv.CV_THRESH_BINARY_INV)
cv.Threshold(b, b, thresh[2], maxv, cv.CV_THRESH_BINARY_INV)
cv.Add(r, g, r)
cv.Add(r, b, r)
return Image(r, colorSpace=self._colorSpace)
elif thresh == -1:
newbitmap = self.getEmpty(1)
if blocksize:
cv.AdaptiveThreshold(self._getGrayscaleBitmap(), newbitmap, maxv,
cv.CV_ADAPTIVE_THRESH_GAUSSIAN_C, cv.CV_THRESH_BINARY_INV, blocksize, p)
else:
cv.Threshold(self._getGrayscaleBitmap(), newbitmap, thresh, float(maxv), cv.CV_THRESH_BINARY_INV + cv.CV_THRESH_OTSU)
return Image(newbitmap, colorSpace=self._colorSpace)
else:
newbitmap = self.getEmpty(1)
#desaturate the image, and apply the new threshold
cv.Threshold(self._getGrayscaleBitmap(), newbitmap, thresh, float(maxv), cv.CV_THRESH_BINARY_INV)
return Image(newbitmap, colorSpace=self._colorSpace)
def meanColor(self):
"""
**SUMMARY**
This method finds the average color of all the pixels in the image.
**RETURNS**
A tuple of the average image values. Tuples are in the channel order. *For most images this means the results are (B,G,R).*
**EXAMPLE**
>>> img = Image('lenna')
>>> colors = img.meanColor()
"""
# I changed this to keep channel order - KAS
return tuple(cv.Avg(self.getBitmap())[0:3])
def findCorners(self, maxnum = 50, minquality = 0.04, mindistance = 1.0):
"""
**SUMMARY**
This will find corner Feature objects and return them as a FeatureSet
strongest corners first. The parameters give the number of corners to look
for, the minimum quality of the corner feature, and the minimum distance
between corners.
**PARAMETERS**
* *maxnum* - The maximum number of corners to return.
* *minquality* - The minimum quality metric. This shoudl be a number between zero and one.
* *mindistance* - The minimum distance, in pixels, between successive corners.
**RETURNS**
A featureset of :py:class:`Corner` features or None if no corners are found.
**EXAMPLE**
Standard Test:
>>> img = Image("sampleimages/simplecv.png")
>>> corners = img.findCorners()
>>> if corners: True
True
Validation Test:
>>> img = Image("sampleimages/black.png")
>>> corners = img.findCorners()
>>> if not corners: True
True
**SEE ALSO**
:py:class:`Corner`
:py:meth:`findKeypoints`
"""
#initialize buffer frames
eig_image = cv.CreateImage(cv.GetSize(self.getBitmap()), cv.IPL_DEPTH_32F, 1)
temp_image = cv.CreateImage(cv.GetSize(self.getBitmap()), cv.IPL_DEPTH_32F, 1)
corner_coordinates = cv.GoodFeaturesToTrack(self._getGrayscaleBitmap(), eig_image, temp_image, maxnum, minquality, mindistance, None)
corner_features = []
for (x, y) in corner_coordinates:
corner_features.append(Corner(self, x, y))
return FeatureSet(corner_features)
def findBlobs(self, threshval = -1, minsize=10, maxsize=0, threshblocksize=0, threshconstant=5,appx_level=3):
"""
**SUMMARY**
Find blobs will look for continuous
light regions and return them as Blob features in a FeatureSet. Parameters
specify the binarize filter threshold value, and minimum and maximum size for blobs.
If a threshold value is -1, it will use an adaptive threshold. See binarize() for
more information about thresholding. The threshblocksize and threshconstant
parameters are only used for adaptive threshold.
**PARAMETERS**
* *threshval* - the threshold as an integer or an (r,g,b) tuple , where pixels below (darker) than thresh are set to to max value,
and all values above this value are set to black. If this parameter is -1 we use Otsu's method.
* *minsize* - the minimum size of the blobs, in pixels, of the returned blobs. This helps to filter out noise.
* *maxsize* - the maximim size of the blobs, in pixels, of the returned blobs.
* *threshblocksize* - the size of the block used in the adaptive binarize operation. *TODO - make this match binarize*
* *appx_level* - The blob approximation level - an integer for the maximum distance between the true edge and the
approximation edge - lower numbers yield better approximation.
.. Warning::
This parameter must be an odd number.
* *threshconstant* - The difference from the local mean to use for thresholding in Otsu's method. *TODO - make this match binarize*
**RETURNS**
Returns a featureset (basically a list) of :py:class:`blob` features. If no blobs are found this method returns None.
**EXAMPLE**
>>> img = Image("lenna")
>>> fs = img.findBlobs()
>>> if( fs is not None ):
>>> fs.draw()
**NOTES**
.. Warning::
For blobs that live right on the edge of the image OpenCV reports the position and width
height as being one over for the true position. E.g. if a blob is at (0,0) OpenCV reports
its position as (1,1). Likewise the width and height for the other corners is reported as
being one less than the width and height. This is a known bug.
**SEE ALSO**
:py:meth:`threshold`
:py:meth:`binarize`
:py:meth:`invert`
:py:meth:`dilate`
:py:meth:`erode`
:py:meth:`findBlobsFromPalette`
:py:meth:`smartFindBlobs`
"""
if (maxsize == 0):
maxsize = self.width * self.height
#create a single channel image, thresholded to parameters
blobmaker = BlobMaker()
blobs = blobmaker.extractFromBinary(self.binarize(threshval, 255, threshblocksize, threshconstant).invert(),
self, minsize = minsize, maxsize = maxsize,appx_level=appx_level)
if not len(blobs):
return None
return FeatureSet(blobs).sortArea()
def findSkintoneBlobs(self, minsize=10, maxsize=0,dilate_iter=1):
"""
**SUMMARY**
Find Skintone blobs will look for continuous
regions of Skintone in a color image and return them as Blob features in a FeatureSet.
Parameters specify the binarize filter threshold value, and minimum and maximum size for
blobs. If a threshold value is -1, it will use an adaptive threshold. See binarize() for
more information about thresholding. The threshblocksize and threshconstant
parameters are only used for adaptive threshold.
**PARAMETERS**
* *minsize* - the minimum size of the blobs, in pixels, of the returned blobs. This helps to filter out noise.
* *maxsize* - the maximim size of the blobs, in pixels, of the returned blobs.
* *dilate_iter* - the number of times to run the dilation operation.
**RETURNS**
Returns a featureset (basically a list) of :py:class:`blob` features. If no blobs are found this method returns None.
**EXAMPLE**
>>> img = Image("lenna")
>>> fs = img.findSkintoneBlobs()
>>> if( fs is not None ):
>>> fs.draw()
**NOTES**
It will be really awesome for making UI type stuff, where you want to track a hand or a face.
**SEE ALSO**
:py:meth:`threshold`
:py:meth:`binarize`
:py:meth:`invert`
:py:meth:`dilate`
:py:meth:`erode`
:py:meth:`findBlobsFromPalette`
:py:meth:`smartFindBlobs`
"""
if (maxsize == 0):
maxsize = self.width * self.height
mask = self.getSkintoneMask(dilate_iter)
blobmaker = BlobMaker()
blobs = blobmaker.extractFromBinary(mask, self, minsize = minsize, maxsize = maxsize)
if not len(blobs):
return None
return FeatureSet(blobs).sortArea()
def getSkintoneMask(self, dilate_iter=0):
"""
**SUMMARY**
Find Skintone mask will look for continuous
regions of Skintone in a color image and return a binary mask where the white pixels denote Skintone region.
**PARAMETERS**
* *dilate_iter* - the number of times to run the dilation operation.
**RETURNS**
Returns a binary mask.
**EXAMPLE**
>>> img = Image("lenna")
>>> mask = img.findSkintoneMask()
>>> mask.show()
"""
if( self._colorSpace != ColorSpace.YCrCb ):
YCrCb = self.toYCrCb()
else:
YCrCb = self
Y = np.ones((256,1),dtype=uint8)*0
Y[5:] = 255
Cr = np.ones((256,1),dtype=uint8)*0
Cr[140:180] = 255
Cb = np.ones((256,1),dtype=uint8)*0
Cb[77:135] = 255
Y_img = YCrCb.getEmpty(1)
Cr_img = YCrCb.getEmpty(1)
Cb_img = YCrCb.getEmpty(1)
cv.Split(YCrCb.getBitmap(),Y_img,Cr_img,Cb_img,None)
cv.LUT(Y_img,Y_img,cv.fromarray(Y))
cv.LUT(Cr_img,Cr_img,cv.fromarray(Cr))
cv.LUT(Cb_img,Cb_img,cv.fromarray(Cb))
temp = self.getEmpty()
cv.Merge(Y_img,Cr_img,Cb_img,None,temp)
mask=Image(temp,colorSpace = ColorSpace.YCrCb)
mask = mask.binarize((128,128,128))
mask = mask.toRGB().binarize()
mask.dilate(dilate_iter)
return mask
#this code is based on code that's based on code from
#http://blog.jozilla.net/2008/06/27/fun-with-python-opencv-and-face-detection/
def findHaarFeatures(self, cascade, scale_factor=1.2, min_neighbors=2, use_canny=cv.CV_HAAR_DO_CANNY_PRUNING, min_size=(20,20)):
"""
**SUMMARY**
A Haar like feature cascase is a really robust way of finding the location
of a known object. This technique works really well for a few specific applications
like face, pedestrian, and vehicle detection. It is worth noting that this
approach **IS NOT A MAGIC BULLET** . Creating a cascade file requires a large
number of images that have been sorted by a human.vIf you want to find Haar
Features (useful for face detection among other purposes) this will return
Haar feature objects in a FeatureSet.
For more information, consult the cv.HaarDetectObjects documentation.
To see what features are available run img.listHaarFeatures() or you can
provide your own haarcascade file if you have one available.
Note that the cascade parameter can be either a filename, or a HaarCascade
loaded with cv.Load(), or a SimpleCV HaarCascade object.
**PARAMETERS**
* *cascade* - The Haar Cascade file, this can be either the path to a cascade
file or a HaarCascased SimpleCV object that has already been
loaded.
* *scale_factor* - The scaling factor for subsequent rounds of the Haar cascade
(default 1.2) in terms of a percentage (i.e. 1.2 = 20% increase in size)
* *min_neighbors* - The minimum number of rectangles that makes up an object. Ususally
detected faces are clustered around the face, this is the number
of detections in a cluster that we need for detection. Higher
values here should reduce false positives and decrease false negatives.
* *use-canny* - Whether or not to use Canny pruning to reject areas with too many edges
(default yes, set to 0 to disable)
* *min_size* - Minimum window size. By default, it is set to the size
of samples the classifier has been trained on ((20,20) for face detection)
**RETURNS**
A feature set of HaarFeatures
**EXAMPLE**
>>> faces = HaarCascade("./SimpleCV/Features/HaarCascades/face.xml","myFaces")
>>> cam = Camera()
>>> while True:
>>> f = cam.getImage().findHaarFeatures(faces)
>>> if( f is not None ):
>>> f.show()
**NOTES**
OpenCV Docs:
- http://opencv.willowgarage.com/documentation/python/objdetect_cascade_classification.html
Wikipedia:
- http://en.wikipedia.org/wiki/Viola-Jones_object_detection_framework
- http://en.wikipedia.org/wiki/Haar-like_features
The video on this pages shows how Haar features and cascades work to located faces:
- http://dismagazine.com/dystopia/evolved-lifestyles/8115/anti-surveillance-how-to-hide-from-machines/
"""
storage = cv.CreateMemStorage(0)
#lovely. This segfaults if not present
if isinstance(cascade, basestring):
from SimpleCV.Features.HaarCascade import HaarCascade
cascade = HaarCascade(cascade)
if not cascade.getCascade(): return None
# added all of the arguments from the opencv docs arglist
objects = cv.HaarDetectObjects(self._getEqualizedGrayscaleBitmap(),
cascade.getCascade(), storage, scale_factor, min_neighbors,
use_canny, min_size)
if objects:
return FeatureSet([HaarFeature(self, o, cascade) for o in objects])
return None
def drawCircle(self, ctr, rad, color = (0, 0, 0), thickness = 1):
"""
**SUMMARY**
Draw a circle on the image.
**PARAMETERS**
* *ctr* - The center of the circle as an (x,y) tuple.
* *rad* - The radius of the circle in pixels
* *color* - A color tuple (default black)
* *thickness* - The thickness of the circle, -1 means filled in.
**RETURNS**
.. Warning::
This is an inline operation. Nothing is returned, but a circle is drawn on the images's
drawing layer.
**EXAMPLE**
>>> img = Image("lenna")
>>> img.drawCircle((img.width/2,img.height/2),r=50,color=Colors.RED,width=3)
>>> img.show()
**NOTES**
.. Warning::
Note that this function is depricated, try to use DrawingLayer.circle() instead.
**SEE ALSO**
:py:meth:`drawLine`
:py:meth:`drawText`
:py:meth:`dl`
:py:meth:`drawRectangle`
:py:class:`DrawingLayer`
"""
if( thickness < 0):
self.getDrawingLayer().circle((int(ctr[0]), int(ctr[1])), int(rad), color, int(thickness),filled=True)
else:
self.getDrawingLayer().circle((int(ctr[0]), int(ctr[1])), int(rad), color, int(thickness))
def drawLine(self, pt1, pt2, color = (0, 0, 0), thickness = 1):
"""
**SUMMARY**
Draw a line on the image.
**PARAMETERS**
* *pt1* - the first point for the line (tuple).
* *pt2* - the second point on the line (tuple).
* *color* - a color tuple (default black).
* *thickness* the thickness of the line in pixels.
**RETURNS**
.. Warning::
This is an inline operation. Nothing is returned, but a circle is drawn on the images's
drawing layer.
**EXAMPLE**
>>> img = Image("lenna")
>>> img.drawLine((0,0),(img.width,img.height),color=Color.RED,thickness=3)
>>> img.show()
**NOTES**
.. Warning::
Note that this function is depricated, try to use DrawingLayer.line() instead.
**SEE ALSO**
:py:meth:`drawText`
:py:meth:`dl`
:py:meth:`drawCircle`
:py:meth:`drawRectangle`
"""
pt1 = (int(pt1[0]), int(pt1[1]))
pt2 = (int(pt2[0]), int(pt2[1]))
self.getDrawingLayer().line(pt1, pt2, color, thickness)
def size(self):
"""
**SUMMARY**
Returns a tuple that lists the width and height of the image.
**RETURNS**
The width and height as a tuple.
"""
if self.width and self.height:
return cv.GetSize(self.getBitmap())
else:
return (0, 0)
def isEmpty(self):
"""
**SUMMARY**
Checks if the image is empty by checking its width and height.
**RETURNS**
True if the image's size is (0, 0), False for any other size.
"""
return self.size() == (0, 0)
def split(self, cols, rows):
"""
**SUMMARY**
This method can be used to brak and image into a series of image chunks.
Given number of cols and rows, splits the image into a cols x rows 2d array
of cropped images
**PARAMETERS**
* *rows* - an integer number of rows.
* *cols* - an integer number of cols.
**RETURNS**
A list of SimpleCV images.
**EXAMPLE**
>>> img = Image("lenna")
>>> quadrant =img.split(2,2)
>>> for f in quadrant:
>>> f.show()
>>> time.sleep(1)
**NOTES**
TODO: This should return and ImageList
"""
crops = []
wratio = self.width / cols
hratio = self.height / rows
for i in range(rows):
row = []
for j in range(cols):
row.append(self.crop(j * wratio, i * hratio, wratio, hratio))
crops.append(row)
return crops
def splitChannels(self, grayscale = True):
"""
**SUMMARY**
Split the channels of an image into RGB (not the default BGR)
single parameter is whether to return the channels as grey images (default)
or to return them as tinted color image
**PARAMETERS**
* *grayscale* - If this is true we return three grayscale images, one per channel.
if it is False return tinted images.
**RETURNS**
A tuple of of 3 image objects.
**EXAMPLE**
>>> img = Image("lenna")
>>> data = img.splitChannels()
>>> for d in data:
>>> d.show()
>>> time.sleep(1)
**SEE ALSO**
:py:meth:`mergeChannels`
"""
r = self.getEmpty(1)
g = self.getEmpty(1)
b = self.getEmpty(1)
cv.Split(self.getBitmap(), b, g, r, None)
red = self.getEmpty()
green = self.getEmpty()
blue = self.getEmpty()
if (grayscale):
cv.Merge(r, r, r, None, red)
cv.Merge(g, g, g, None, green)
cv.Merge(b, b, b, None, blue)
else:
cv.Merge(None, None, r, None, red)
cv.Merge(None, g, None, None, green)
cv.Merge(b, None, None, None, blue)
return (Image(red), Image(green), Image(blue))
def mergeChannels(self,r=None,b=None,g=None):
"""
**SUMMARY**
Merge channels is the oposite of splitChannels. The image takes one image for each
of the R,G,B channels and then recombines them into a single image. Optionally any of these
channels can be None.
**PARAMETERS**
* *r* - The r or last channel of the result SimpleCV Image.
* *g* - The g or center channel of the result SimpleCV Image.
* *b* - The b or first channel of the result SimpleCV Image.
**RETURNS**
A SimpleCV Image.
**EXAMPLE**
>>> img = Image("lenna")
>>> [r,g,b] = img.splitChannels()
>>> r = r.binarize()
>>> g = g.binarize()
>>> b = b.binarize()
>>> result = img.mergeChannels(r,g,b)
>>> result.show()
**SEE ALSO**
:py:meth:`splitChannels`
"""
if( r is None and g is None and b is None ):
logger.warning("ImageClass.mergeChannels - we need at least one valid channel")
return None
if( r is None ):
r = self.getEmpty(1)
cv.Zero(r);
else:
rt = r.getEmpty(1)
cv.Split(r.getBitmap(),rt,rt,rt,None)
r = rt
if( g is None ):
g = self.getEmpty(1)
cv.Zero(g);
else:
gt = g.getEmpty(1)
cv.Split(g.getBitmap(),gt,gt,gt,None)
g = gt
if( b is None ):
b = self.getEmpty(1)
cv.Zero(b);
else:
bt = b.getEmpty(1)
cv.Split(b.getBitmap(),bt,bt,bt,None)
b = bt
retVal = self.getEmpty()
cv.Merge(b,g,r,None,retVal)
return Image(retVal);
def applyHLSCurve(self, hCurve, lCurve, sCurve):
"""
**SUMMARY**
Apply a color correction curve in HSL space. This method can be used
to change values for each channel. The curves are :py:class:`ColorCurve` class objects.
**PARAMETERS**
* *hCurve* - the hue ColorCurve object.
* *lCurve* - the lightnes / value ColorCurve object.
* *sCurve* - the saturation ColorCurve object
**RETURNS**
A SimpleCV Image
**EXAMPLE**
>>> img = Image("lenna")
>>> hc = ColorCurve([[0,0], [100, 120], [180, 230], [255, 255]])
>>> lc = ColorCurve([[0,0], [90, 120], [180, 230], [255, 255]])
>>> sc = ColorCurve([[0,0], [70, 110], [180, 230], [240, 255]])
>>> img2 = img.applyHLSCurve(hc,lc,sc)
**SEE ALSO**
:py:class:`ColorCurve`
:py:meth:`applyRGBCurve`
"""
#TODO CHECK ROI
#TODO CHECK CURVE SIZE
#TODO CHECK COLORSPACE
#TODO CHECK CURVE SIZE
temp = cv.CreateImage(self.size(), 8, 3)
#Move to HLS space
cv.CvtColor(self._bitmap, temp, cv.CV_RGB2HLS)
tempMat = cv.GetMat(temp) #convert the bitmap to a matrix
#now apply the color curve correction
tempMat = np.array(self.getMatrix()).copy()
tempMat[:, :, 0] = np.take(hCurve.mCurve, tempMat[:, :, 0])
tempMat[:, :, 1] = np.take(sCurve.mCurve, tempMat[:, :, 1])
tempMat[:, :, 2] = np.take(lCurve.mCurve, tempMat[:, :, 2])
#Now we jimmy the np array into a cvMat
image = cv.CreateImageHeader((tempMat.shape[1], tempMat.shape[0]), cv.IPL_DEPTH_8U, 3)
cv.SetData(image, tempMat.tostring(), tempMat.dtype.itemsize * 3 * tempMat.shape[1])
cv.CvtColor(image, image, cv.CV_HLS2RGB)
return Image(image, colorSpace=self._colorSpace)
def applyRGBCurve(self, rCurve, gCurve, bCurve):
"""
**SUMMARY**
Apply a color correction curve in RGB space. This method can be used
to change values for each channel. The curves are :py:class:`ColorCurve` class objects.
**PARAMETERS**
* *rCurve* - the red ColorCurve object.
* *gCurve* - the green ColorCurve object.
* *bCurve* - the blue ColorCurve object.
**RETURNS**
A SimpleCV Image
**EXAMPLE**
>>> img = Image("lenna")
>>> rc = ColorCurve([[0,0], [100, 120], [180, 230], [255, 255]])
>>> gc = ColorCurve([[0,0], [90, 120], [180, 230], [255, 255]])
>>> bc = ColorCurve([[0,0], [70, 110], [180, 230], [240, 255]])
>>> img2 = img.applyRGBCurve(rc,gc,bc)
**SEE ALSO**
:py:class:`ColorCurve`
:py:meth:`applyHLSCurve`
"""
tempMat = np.array(self.getMatrix()).copy()
tempMat[:, :, 0] = np.take(bCurve.mCurve, tempMat[:, :, 0])
tempMat[:, :, 1] = np.take(gCurve.mCurve, tempMat[:, :, 1])
tempMat[:, :, 2] = np.take(rCurve.mCurve, tempMat[:, :, 2])
#Now we jimmy the np array into a cvMat
image = cv.CreateImageHeader((tempMat.shape[1], tempMat.shape[0]), cv.IPL_DEPTH_8U, 3)
cv.SetData(image, tempMat.tostring(), tempMat.dtype.itemsize * 3 * tempMat.shape[1])
return Image(image, colorSpace=self._colorSpace)
def applyIntensityCurve(self, curve):
"""
**SUMMARY**
Intensity applied to all three color channels
**PARAMETERS**
* *curve* - a ColorCurve object.
**RETURNS**
A SimpleCV Image
**EXAMPLE**
>>> img = Image("lenna")
>>> rc = ColorCurve([[0,0], [100, 120], [180, 230], [255, 255]])
>>> gc = ColorCurve([[0,0], [90, 120], [180, 230], [255, 255]])
>>> bc = ColorCurve([[0,0], [70, 110], [180, 230], [240, 255]])
>>> img2 = img.applyRGBCurve(rc,gc,bc)
**SEE ALSO**
:py:class:`ColorCurve`
:py:meth:`applyHLSCurve`
"""
return self.applyRGBCurve(curve, curve, curve)
def colorDistance(self, color = Color.BLACK):
"""
**SUMMARY**
Returns an image representing the distance of each pixel from a given color
tuple, scaled between 0 (the given color) and 255. Pixels distant from the
given tuple will appear as brighter and pixels closest to the target color
will be darker.
By default this will give image intensity (distance from pure black)
**PARAMETERS**
* *color* - Color object or Color Tuple
**RETURNS**
A SimpleCV Image.
**EXAMPLE**
>>> img = Image("logo")
>>> img2 = img.colorDistance(color=Color.BLACK)
>>> img2.show()
**SEE ALSO**
:py:meth:`binarize`
:py:meth:`hueDistance`
:py:meth:`findBlobsFromMask`
"""
pixels = np.array(self.getNumpy()).reshape(-1, 3) #reshape our matrix to 1xN
distances = spsd.cdist(pixels, [color]) #calculate the distance each pixel is
distances *= (255.0/distances.max()) #normalize to 0 - 255
return Image(distances.reshape(self.width, self.height)) #return an Image
def hueDistance(self, color = Color.BLACK, minsaturation = 20, minvalue = 20):
"""
**SUMMARY**
Returns an image representing the distance of each pixel from the given hue
of a specific color. The hue is "wrapped" at 180, so we have to take the shorter
of the distances between them -- this gives a hue distance of max 90, which we'll
scale into a 0-255 grayscale image.
The minsaturation and minvalue are optional parameters to weed out very weak hue
signals in the picture, they will be pushed to max distance [255]
**PARAMETERS**
* *color* - Color object or Color Tuple.
* *minsaturation* - the minimum saturation value for color (from 0 to 255).
* *minvalue* - the minimum hue value for the color (from 0 to 255).
**RETURNS**
A simpleCV image.
**EXAMPLE**
>>> img = Image("logo")
>>> img2 = img.hueDistance(color=Color.BLACK)
>>> img2.show()
**SEE ALSO**
:py:meth:`binarize`
:py:meth:`hueDistance`
:py:meth:`morphOpen`
:py:meth:`morphClose`
:py:meth:`morphGradient`
:py:meth:`findBlobsFromMask`
"""
if isinstance(color, (float,int,long,complex)):
color_hue = color
else:
color_hue = Color.hsv(color)[0]
vsh_matrix = self.toHSV().getNumpy().reshape(-1,3) #again, gets transposed to vsh
hue_channel = np.cast['int'](vsh_matrix[:,2])
if color_hue < 90:
hue_loop = 180
else:
hue_loop = -180
#set whether we need to move back or forward on the hue circle
distances = np.minimum( np.abs(hue_channel - color_hue), np.abs(hue_channel - (color_hue + hue_loop)))
#take the minimum distance for each pixel
distances = np.where(
np.logical_and(vsh_matrix[:,0] > minvalue, vsh_matrix[:,1] > minsaturation),
distances * (255.0 / 90.0), #normalize 0 - 90 -> 0 - 255
255.0) #use the maxvalue if it false outside of our value/saturation tolerances
return Image(distances.reshape(self.width, self.height))
def erode(self, iterations=1):
"""
**SUMMARY**
Apply a morphological erosion. An erosion has the effect of removing small bits of noise
and smothing blobs.
This implementation uses the default openCV 3X3 square kernel
Erosion is effectively a local minima detector, the kernel moves over the image and
takes the minimum value inside the kernel.
iterations - this parameters is the number of times to apply/reapply the operation
* See: http://en.wikipedia.org/wiki/Erosion_(morphology).
* See: http://opencv.willowgarage.com/documentation/cpp/image_filtering.html#cv-erode
* Example Use: A threshold/blob image has 'salt and pepper' noise.
* Example Code: /examples/MorphologyExample.py
**PARAMETERS**
* *iterations* - the number of times to run the erosion operation.
**RETURNS**
A SimpleCV image.
**EXAMPLE**
>>> img = Image("lenna")
>>> derp = img.binarize()
>>> derp.erode(3).show()
**SEE ALSO**
:py:meth:`dilate`
:py:meth:`binarize`
:py:meth:`morphOpen`
:py:meth:`morphClose`
:py:meth:`morphGradient`
:py:meth:`findBlobsFromMask`
"""
retVal = self.getEmpty()
kern = cv.CreateStructuringElementEx(3, 3, 1, 1, cv.CV_SHAPE_RECT)
cv.Erode(self.getBitmap(), retVal, kern, iterations)
return Image(retVal, colorSpace=self._colorSpace)
def dilate(self, iterations=1):
"""
**SUMMARY**
Apply a morphological dilation. An dilation has the effect of smoothing blobs while
intensifying the amount of noise blobs.
This implementation uses the default openCV 3X3 square kernel
Erosion is effectively a local maxima detector, the kernel moves over the image and
takes the maxima value inside the kernel.
* See: http://en.wikipedia.org/wiki/Dilation_(morphology)
* See: http://opencv.willowgarage.com/documentation/cpp/image_filtering.html#cv-dilate
* Example Use: A part's blob needs to be smoother
* Example Code: ./examples/MorphologyExample.py
**PARAMETERS**
* *iterations* - the number of times to run the dilation operation.
**RETURNS**
A SimpleCV image.
**EXAMPLE**
>>> img = Image("lenna")
>>> derp = img.binarize()
>>> derp.dilate(3).show()
**SEE ALSO**
:py:meth:`erode`
:py:meth:`binarize`
:py:meth:`morphOpen`
:py:meth:`morphClose`
:py:meth:`morphGradient`
:py:meth:`findBlobsFromMask`
"""
retVal = self.getEmpty()
kern = cv.CreateStructuringElementEx(3, 3, 1, 1, cv.CV_SHAPE_RECT)
cv.Dilate(self.getBitmap(), retVal, kern, iterations)
return Image(retVal, colorSpace=self._colorSpace)
def morphOpen(self):
"""
**SUMMARY**
morphologyOpen applies a morphological open operation which is effectively
an erosion operation followed by a morphological dilation. This operation
helps to 'break apart' or 'open' binary regions which are close together.
* `Morphological opening on Wikipedia <http://en.wikipedia.org/wiki/Opening_(morphology)>`_
* `OpenCV documentation <http://opencv.willowgarage.com/documentation/cpp/image_filtering.html#cv-morphologyex>`_
* Example Use: two part blobs are 'sticking' together.
* Example Code: ./examples/MorphologyExample.py
**RETURNS**
A SimpleCV image.
**EXAMPLE**
>>> img = Image("lenna")
>>> derp = img.binarize()
>>> derp.morphOpen.show()
**SEE ALSO**
:py:meth:`erode`
:py:meth:`dilate`
:py:meth:`binarize`
:py:meth:`morphClose`
:py:meth:`morphGradient`
:py:meth:`findBlobsFromMask`
"""
retVal = self.getEmpty()
temp = self.getEmpty()
kern = cv.CreateStructuringElementEx(3, 3, 1, 1, cv.CV_SHAPE_RECT)
try:
cv.MorphologyEx(self.getBitmap(), retVal, temp, kern, cv.MORPH_OPEN, 1)
except:
cv.MorphologyEx(self.getBitmap(), retVal, temp, kern, cv.CV_MOP_OPEN, 1)
#OPENCV 2.2 vs 2.3 compatability
return( Image(retVal) )
def morphClose(self):
"""