1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 '''
35
36 '''
37 __author__ = "$Author$"
38 __version__ = "$Revision$"
39
40
41 import PIL.ImageDraw
42 import PIL.Image
43 from PIL.Image import BICUBIC, ANTIALIAS
44 import PIL.ImageFont as ImageFont
45
46
47 import numpy
48 import numpy as np
49 import cv
50 import cv2
51 import pyvision
52 import pyvision as pv
53
54 import cStringIO
55 import exif
56 import os
57
58
59 try:
60 import pylab
61 import IPython
62 except:
63 pass
64
65
66 TYPE_MATRIX_2D = "TYPE_MATRIX2D"
67 '''Image was created using a 2D "gray-scale" numpy array'''
68
69 TYPE_MATRIX_RGB = "TYPE_MATRIX_RGB"
70 '''Image was created using a 3D "color" numpy array'''
71
72 TYPE_PIL = "TYPE_PIL"
73 '''Image was created using a PIL image instance'''
74
75 TYPE_OPENCV = "TYPE_OPENCV"
76 '''Image was created using a OpenCV image instance'''
77
78 TYPE_OPENCV2 = "TYPE_OPENCV2"
79 '''Image was created using a OpenCV image instance'''
80
81 TYPE_OPENCV2BW = "TYPE_OPENCV2BW"
82 '''Image was created using a OpenCV image instance'''
83
84 LUMA = [0.299, 0.587, 0.114, 1.0]
85 '''Values used when converting color to gray-scale.'''
86
88 '''
89 The primary purpose of the image class is to provide a structure that can
90 transform an image back and fourth for different python libraries such as
91 U{PIL<http://www.pythonware.com/products/pil>},
92 U{OpenCV <http://sourceforge.net/projects/opencvlibrary>}, and
93 U{Scipy<http://www.scipy.org">} Images. This class also
94 allows some simple operations on the image such as annotation.
95
96 B{Note:} When working with images in matrix format, they are transposed such
97 that x = col and y = row. You can therefore still work with coords
98 such that im[x,y] = mat[x,y]. #
99
100 Images have the following attributes:
101 - width = width of the image
102 - height = height of the image
103 - size = (width,height)
104 - channels = number of channels: 1(gray), 3(RGB)
105 - depth = bitdepth: 8(uchar), 32(float), 64(double)
106 '''
107
108
109
110 - def __init__(self,data,bw_annotate=False):
111 '''
112 Create an image from a file or a PIL Image, OpenCV Image, or numpy array.
113
114 @param data: this can be a numpy array, PIL image, or opencv image.
115 @param bw_annotate: generate a black and white image to make color annotations show up better
116 @return: an Image object instance
117 '''
118
119 self.filename = None
120 self.pil = None
121 self.matrix2d = None
122 self.matrix3d = None
123 self.opencv = None
124 self.opencv2 = None
125 self.opencv2bw = None
126 self.annotated = None
127 self.bw_annotate = bw_annotate
128
129
130 if isinstance(data,cv.iplimage) and data.nChannels == 3 and data.depth == 32:
131 w,h = cv.GetSize(data)
132 data = np.frombuffer(data.tostring(),dtype=np.float32)
133 data.shape = (h,w,3)
134 data = data.transpose((2,1,0))
135 data = data[::-1,:,:]
136
137
138 if isinstance(data,cv.iplimage) and data.nChannels == 1 and data.depth == 32:
139 w,h = cv.GetSize(data)
140 data = np.frombuffer(data.tostring(),dtype=np.float32)
141 data.shape = (h,w)
142 data = data.transpose((2,1,0))
143 data = data[::-1,:,:]
144
145
146 if isinstance(data,numpy.ndarray) and len(data.shape) == 2 and data.dtype != np.uint8:
147 self.type=TYPE_MATRIX_2D
148 self.matrix2d = data
149
150 self.width,self.height = self.matrix2d.shape
151 self.channels = 1
152
153 if self.matrix2d.dtype == numpy.float32:
154 self.depth=32
155 elif self.matrix2d.dtype == numpy.float64:
156 self.depth=64
157 else:
158 raise TypeError("Unsuppoted format for ndarray images: %s"%self.matrix2d.dtype)
159
160
161 elif isinstance(data,numpy.ndarray) and len(data.shape) == 2 and data.dtype == np.uint8:
162 self.type=TYPE_OPENCV2BW
163 self.opencv2bw = data
164
165 self.height,self.width = self.opencv2bw.shape
166 self.channels = 1
167 self.depth=8
168
169
170 elif isinstance(data,numpy.ndarray) and len(data.shape) == 3 and data.shape[0]==3 and data.dtype != np.uint8:
171 self.type=TYPE_MATRIX_RGB
172 self.matrix3d = data
173 self.channels=3
174 self.width = self.matrix3d.shape[1]
175 self.height = self.matrix3d.shape[2]
176
177 if self.matrix3d.dtype == numpy.float32:
178 self.depth=32
179 elif self.matrix3d.dtype == numpy.float64:
180 self.depth=64
181 else:
182 raise TypeError("Unsuppoted format for ndarray images: %s"%self.matrix2d.dtype)
183
184
185 elif isinstance(data,numpy.ndarray) and len(data.shape) == 3 and data.shape[2]==3 and data.dtype == np.uint8:
186 self.type=TYPE_OPENCV2
187 self.opencv2 = data
188 self.channels=3
189 self.width = self.opencv2.shape[1]
190 self.height = self.opencv2.shape[0]
191 self.depth=8
192
193
194 elif isinstance(data,PIL.Image.Image) or type(data) == str:
195 if type(data) == str:
196
197
198
199 self.filename = data
200 data = PIL.Image.open(data)
201
202 self.type=TYPE_PIL
203 self.pil = data
204 self.width,self.height = self.pil.size
205
206 if self.pil.mode == 'L':
207 self.channels = 1
208 elif self.pil.mode == 'RGB':
209 self.channels = 3
210
211
212
213
214 else:
215 self.pil.convert('RGB')
216 self.channels = 3
217
218
219 self.depth = 8
220
221
222 elif isinstance(data,cv.iplimage):
223 self.type=TYPE_OPENCV
224 self.opencv=data
225
226 self.width = data.width
227 self.height = data.height
228
229 assert data.nChannels in (1,3)
230 self.channels = data.nChannels
231
232 assert data.depth == 8
233 self.depth = data.depth
234
235
236 else:
237 raise TypeError("Could not create from type: %s %s"%(data,type(data)))
238
239 self.size = (self.width,self.height)
240 self.data = data
241
243 '''
244 @return: a gray-scale version of this pyvision image
245 '''
246 if self.matrix2d is None:
247 self._generateMatrix2D()
248 return Image(self.matrix2d)
249
251 '''
252 @return: the gray-scale image data as a two dimensional numpy array
253 '''
254 if self.matrix2d is None:
255 self._generateMatrix2D()
256 return self.matrix2d
257
259 '''
260 @return: color image data as a 3D array with shape (3(rgb),w,h)
261 '''
262 if self.matrix3d is None:
263 self._generateMatrix3D()
264 return self.matrix3d
265
267 '''
268 @return: image data as a pil image
269 '''
270 if self.pil is None:
271 self._generatePIL()
272 return self.pil
273
275 '''
276 @return: the image data in an OpenCV format
277 '''
278 if self.opencv is None:
279 self._generateOpenCV()
280 return self.opencv
281
283 '''
284 @return: the image data in an OpenCV format that is a numpy array of shape (h,w,3) of uint8
285 '''
286 if self.opencv2 is None:
287 self._generateOpenCV2()
288 return self.opencv2
289
291 '''
292 @return: the image data in an OpenCV format that is a numpy array of shape (h,w,1) of uint8
293 '''
294 if self.opencv2bw is None:
295 self._generateOpenCV2BW()
296 return self.opencv2bw
297
299 '''
300 @return: the image data in an OpenCV one channel format
301 '''
302 cvim = self.asOpenCV()
303
304 if cvim.nChannels == 1:
305 return cvim
306
307 elif cvim.nChannels == 3:
308 cvimbw = cv.CreateImage(cv.GetSize(cvim), cv.IPL_DEPTH_8U, 1);
309 cv.CvtColor(cvim, cvimbw, cv.CV_BGR2GRAY);
310 return cvimbw
311
312 else:
313 raise ValueError("Unsupported opencv image format: nChannels=%d"%cvim.nChannels)
314
316 '''
317 @returns: a thermal colored representation of this image.
318 '''
319 w,h = self.size
320 mat = self.asMatrix2D()
321 if clip_negative:
322 mat = mat*(mat > 0.0)
323
324
325 mat = mat - mat.min()
326 mat = mat / mat.max()
327
328 therm = np.zeros((3,w,h),dtype=np.float)
329
330
331 mask = mat <= 0.1
332 therm[2,:,:] += mask*(0.5 + 0.5*mat/0.1)
333
334
335 mask = (mat > 0.10) & (mat <= 0.4)
336 tmp = (mat - 0.10) / 0.30
337 therm[2,:,:] += mask*(1.0-tmp)
338 therm[1,:,:] += mask*tmp
339 therm[0,:,:] += mask*tmp
340
341
342 mask = (mat > 0.4) & (mat <= 0.7)
343 tmp = (mat - 0.4) / 0.3
344 therm[2,:,:] += mask*0
345 therm[1,:,:] += mask*(1-0.5*tmp)
346 therm[0,:,:] += mask*1
347
348
349 mask = (mat > 0.7)
350 tmp = (mat - 0.7) / 0.3
351 therm[2,:,:] += mask*0
352 therm[1,:,:] += mask*(0.5-0.5*tmp)
353 therm[0,:,:] += mask*1
354
355 return pv.Image(therm)
356
357
359 '''
360 @param as_type: Specify either "PIL" or "PV". If
361 "PIL" (default) then the return type is a PIL image.
362 If "PV", then the return type is a pyvision image,
363 where the annotations have been 'flattened' onto
364 the original source image.
365 @return: the PIL image used for annotation.
366 '''
367 if self.annotated is None:
368 if self.bw_annotate:
369
370 self.annotated = self.asPIL().convert("L").copy().convert("RGB")
371 else:
372
373 self.annotated = self.asPIL().copy().convert("RGB")
374
375 if as_type.upper() == "PV":
376 return pv.Image(self.annotated)
377 else:
378 return self.annotated
379
381 '''
382 @return: an OpenCV HSV encoded image
383 '''
384 cvim = self.asOpenCV()
385 dst = cv.CreateImage(cv.GetSize(cvim), cv.IPL_DEPTH_8U, 3)
386 cv.CvtColor(cvim, dst, cv.CV_BGR2HSV)
387
388 return dst
389
390
392 '''
393 @return: an OpenCV LAB encoded image
394 '''
395 cvim = self.asOpenCV()
396 dst = cv.CreateImage(cv.GetSize(cvim), cv.IPL_DEPTH_8U, 3)
397 cv.CvtColor(cvim, dst, cv.CV_BGR2Lab)
398
399 return dst
400
401 - def getExif(self,output='simple'):
402 '''
403 This function returns the exif headers for an image. This only works
404 for images that have been read from disk.
405
406 @param output: select 'simple' or 'full'. 'full' output contains additional metadata.
407 @returns: a dictionary of EXIF data.
408 '''
409 if self.type == TYPE_PIL and self.filename is not None:
410 result = {}
411 info = self.pil._getexif()
412 if info is None:
413 return None
414
415
416 for key,value in info.iteritems():
417 tag = "ukn_%s"%key
418
419 if exif.EXIF_TAGS.has_key(key):
420 tag = exif.EXIF_TAGS[key][0]
421 datatype = exif.EXIF_TAGS[key][1]
422 category = exif.EXIF_TAGS[key][2]
423 description = exif.EXIF_TAGS[key][3]
424
425 if isinstance(value,tuple) and len(value) == 2 and value[1] > 0:
426 value = float(value[0])/float(value[1])
427 if output == 'simple':
428 result[tag] = value
429 else:
430 result[tag] = (value,key,datatype,category,description)
431 return result
432 else:
433 return None
434
435
436 - def annotateRect(self,rect,color='red', fill_color=None, alpha=1.0):
437 '''
438 Draws a rectangle on the annotation image
439
440 @param rect: a rectangle of type Rect
441 @param color: defined as ('#rrggbb' or 'name')
442 @param fill_color: defined as per color, but indicates the color
443 used to fill the rectangle. Specify None for no fill.
444 @param alpha: Ignored if no fill. Otherwise, this value controls
445 how opaque the fill is. Specify 1.0 (default) for a fully opaque
446 fill, or 0.0 for fully transparent. A value of 0.3, for example,
447 would show a partially transparent filled rectangle over
448 the background image.
449 '''
450 im = self.asAnnotated()
451 box = rect.box()
452 offset = (box[0],box[1])
453
454 if fill_color:
455 (r,g,b) = PIL.ImageColor.getrgb(fill_color)
456 rect_img = PIL.Image.new('RGBA', (int(rect.w),int(rect.h)), (r,g,b,int(alpha*255)))
457 im.paste(rect_img,offset,mask=rect_img)
458
459
460 draw = PIL.ImageDraw.Draw(im)
461 draw.rectangle(box,outline=color,fill=None)
462 del draw
463
465 '''
466 Draws an image
467
468 @param im: the image to render
469 @param rect: a rectangle of type Rect
470 @param color: defined as ('#rrggbb' or 'name')
471 @param fill_color: defined as per color, but indicates the color
472 used to fill the rectangle. Specify None for no fill.
473 '''
474
475 thumb = im.thumbnail((rect.w,rect.h))
476 x = int(rect.x + rect.w/2 - thumb.size[0]/2)
477 y = int(rect.y + rect.h/2 - thumb.size[1]/2)
478
479
480 pil = self.asAnnotated()
481
482
483 draw = PIL.ImageDraw.Draw(pil)
484 box = [rect.x,rect.y,rect.x+rect.w,rect.y+rect.h]
485 draw.rectangle(box,outline=None,fill=fill_color)
486 del draw
487
488
489 pil.paste(im.asPIL(),(x,y))
490
491
492 draw = PIL.ImageDraw.Draw(pil)
493 box = [rect.x,rect.y,rect.x+rect.w,rect.y+rect.h]
494 draw.rectangle(box,outline=color,fill=None)
495 del draw
496
498 '''
499 Draws a rectangle on the annotation image
500
501 @param rect: a rectangle of type Rect
502 @param color: defined as ('#rrggbb' or 'name')
503 '''
504
505 im = self.asAnnotated()
506 draw = PIL.ImageDraw.Draw(im)
507 x,y,w,h = [rect.x,rect.y,rect.w,rect.h]
508
509
510 line = [x,y,x+w,y]
511 draw.line(line,fill=color,width=width)
512 line = [x,y,x,y+h]
513 draw.line(line,fill=color,width=width)
514 line = [x,y+h,x+w,y+h]
515 draw.line(line,fill=color,width=width)
516 line = [x+w,y,x+w,y+h]
517 draw.line(line,fill=color,width=width)
518 del draw
519
521 '''
522 Draws an ellipse on the annotation image
523
524 @param rect: the bounding box of the elipse of type Rect
525 @param color: defined as ('#rrggbb' or 'name')
526 '''
527 im = self.asAnnotated()
528 draw = PIL.ImageDraw.Draw(im)
529 box = [rect.x,rect.y,rect.x+rect.w,rect.y+rect.h]
530 draw.ellipse(box,outline=color)
531 del draw
532
534 '''
535 Draws a line from point1 to point2 on the annotation image
536
537 @param point1: the starting point as type Point
538 @param point2: the ending point as type Point
539 @param color: defined as ('#rrggbb' or 'name')
540 '''
541 im = self.asAnnotated()
542 draw = PIL.ImageDraw.Draw(im)
543 line = [point1.X(),point1.Y(),point2.X(),point2.Y()]
544 draw.line(line,fill=color,width=width)
545 del draw
546
548 '''
549 Draws a line from point1 to point2 on the annotation image
550
551 @param point1: the starting point as type Point
552 @param point2: the ending point as type Point
553 @param color: defined as ('#rrggbb' or 'name')
554 '''
555 n = len(points)-1
556 for i in range(n):
557 self.annotateLine(points[i],points[i+1],color=color,width=width)
558
560 '''
561 Shades the contents of a mask.
562
563 @param mask: a numpy array showing the mask.
564 @param color: defined as ('#rrggbb' or 'name')
565 '''
566 im = self.asAnnotated()
567 draw = PIL.ImageDraw.Draw(im)
568 pil = pv.Image(1.0*mask).asPIL()
569 pil = pil.convert('1')
570 draw.bitmap((0,0), pil, fill=color)
571 del draw
572
574 '''
575 Draws a line from point1 to point2 on the annotation image
576
577 @param points: a list of pv points to be plotted
578 @param color: defined as ('#rrggbb' or 'name')
579 @param width: the line width
580 '''
581
582 if fill is not None:
583 im = self.asAnnotated()
584 draw = PIL.ImageDraw.Draw(im)
585 poly = [(point.X(),point.Y()) for point in points]
586 draw.polygon(poly,outline=None,fill=fill)
587 del draw
588
589
590 if color is not None:
591 n = len(points)
592 for i in range(n):
593 j = (i+1)%n
594 self.annotateLine(points[i],points[j],color=color,width=width)
595
597 '''
598 Marks a point in the annotation image using a small circle
599
600 @param point: the point to mark as type Point
601 @param color: defined as ('#rrggbb' or 'name')
602 '''
603 im = self.asAnnotated()
604 draw = PIL.ImageDraw.Draw(im)
605 box = [point.X()-3,point.Y()-3,point.X()+3,point.Y()+3]
606 draw.ellipse(box,outline=color)
607 del draw
608
610 '''
611 Marks a point in the annotation image using a small circle
612
613 @param point: the point to mark as type Point
614 @param color: defined as ('#rrggbb' or 'name')
615 '''
616 im = self.asAnnotated()
617 draw = PIL.ImageDraw.Draw(im)
618 for point in points:
619 box = [point.X()-3,point.Y()-3,point.X()+3,point.Y()+3]
620 draw.ellipse(box,outline=color)
621 del draw
622
624 '''
625 Marks a circle in the annotation image
626
627 @param point: the center of the circle as type Point
628 @param radius: the radius of the circle
629 @param color: defined as ('#rrggbb' or 'name')
630 '''
631 im = self.asAnnotated()
632 draw = PIL.ImageDraw.Draw(im)
633 box = [point.X()-radius,point.Y()-radius,point.X()+radius,point.Y()+radius]
634 draw.ellipse(box,outline=color,fill=fill)
635 del draw
636
637 - def annotateArc(self,point, radius=3, startangle=0, endangle=360, color='red'):
638 '''
639 Draws a circular arc on the image.
640 @param point: the center of the circle as type Point
641 @param radius: the radius of the circle
642 @param startangle: the starting angle of the arc segment to be drawn, in degrees
643 @param endangle: the ending angle in degrees. Arc will be drawn clockwise from
644 starting angle to ending angle.
645 @param color: defined as ('#rrggbb' or 'name')
646 '''
647 im = self.asAnnotated()
648 draw = PIL.ImageDraw.Draw(im)
649 box = [int(point.X()-radius),int(point.Y()-radius),
650 int(point.X()+radius),int(point.Y()+radius)]
651 draw.arc(box, int(startangle), int(endangle), fill=color)
652 del draw
653
654 - def annotateLabel(self,point,label,color='red',mark=False, font=None, background=None):
655 '''
656 Marks a point in the image with text
657
658 @param point: the point to mark as type Point
659 @param label: the text to use as a string
660 @param color: defined as ('#rrggbb' or 'name')
661 @param mark: of True or ['right', 'left', 'below', or 'above','centered'] then also mark the point with a small circle
662 @param font: An optional PIL.ImageFont font object to use. Alternatively, specify an integer and the label
663 will use Arial font of that size. If None, then the default is used.
664 @param background: An optional color that will be used to draw a rectangular background underneath the text.
665 '''
666
667 im = self.asAnnotated()
668 draw = PIL.ImageDraw.Draw(im)
669
670
671 if font is None:
672 font = ImageFont.load_default()
673 elif isinstance(font,int):
674 font = ImageFont.truetype(pv.FONT_ARIAL, font)
675
676
677 tw,th = draw.textsize(label, font=font)
678
679
680 if mark in [True, 'right']:
681 textpt = pv.Point(point.X()+5,point.Y()-th/2)
682 box = [point.X()-3,point.Y()-3,point.X()+3,point.Y()+3]
683 elif mark in ['left']:
684 textpt = pv.Point(point.X()-tw-5,point.Y()-th/2)
685 box = [point.X()-3,point.Y()-3,point.X()+3,point.Y()+3]
686 elif mark in ['below']:
687 textpt = pv.Point(point.X()-tw/2,point.Y()+5)
688 box = [point.X()-3,point.Y()-3,point.X()+3,point.Y()+3]
689 elif mark in ['above']:
690 textpt = pv.Point(point.X()-tw/2,point.Y()-th-5)
691 box = [point.X()-3,point.Y()-3,point.X()+3,point.Y()+3]
692 elif mark in ['centered']:
693 textpt = pv.Point(point.X()-tw/2,point.Y()-th/2)
694 else:
695 textpt = point
696
697
698 if background is not None:
699 point2 = pv.Point( textpt.x + tw, textpt.y+th)
700 draw.rectangle([textpt.asTuple(), point2.asTuple()], fill=background)
701
702
703 draw.text([textpt.x,textpt.y],label,fill=color, font=font)
704
705 if mark not in [False,None,'centered']:
706 draw.ellipse(box,outline=color)
707
708 del draw
709
710
712 '''
713 Like L{annotatePoint} but only draws a point on the given pixel.
714 This is useful to avoid clutter if many points are being annotated.
715
716 @param point: the point to mark as type Point
717 @param color: defined as ('#rrggbb' or 'name')
718 '''
719 im = self.asAnnotated()
720 draw = PIL.ImageDraw.Draw(im)
721 draw.point([point.X(),point.Y()],fill=color)
722 del draw
723
724
726 '''TODO: Deprecated remove this sometime.'''
727 print "WARNING: Image.valueNormalize has been deprecated."
728 return self.normalize()
729
730
732 '''Return the type of the image.'''
733 return self.type
734
735
737 ''' Equalize and normalize the image. '''
738 import PIL.ImageOps
739
740 pil = self.asPIL().copy()
741
742
743 pil = PIL.ImageOps.equalize(pil.convert('L'))
744 self.pil = pil
745 self.matrix2d = None
746
747
748 mat = self.asMatrix2D()
749 mean = mat.mean()
750 std = mat.std()
751 mat -= mean
752 mat /= std
753 self.matrix2d=mat
754
755
757 ''' Equalize the image '''
758 import PIL.ImageOps
759 pil = self.asPIL().copy()
760 if bw:
761 pil = PIL.ImageOps.equalize(pil.convert('L'))
762 else:
763 pil = PIL.ImageOps.equalize(pil)
764 return pv.Image(pil)
765
766
768 '''
769 Create a matrix version of the image.
770 '''
771 data_buffer = self.toBufferGray(32)
772 self.matrix2d = numpy.frombuffer(data_buffer,numpy.float32).reshape(self.height,self.width).transpose()
773
774
776 '''
777 Create a matrix version of the image.
778 '''
779 data_buffer = self.toBufferRGB(32)
780 self.matrix3d = numpy.frombuffer(data_buffer,numpy.float32).reshape(self.height,self.width,3).transpose()
781
783 '''
784 Create a PIL version of the image
785 '''
786 if self.channels == 1:
787 try:
788
789 self.pil = PIL.Image.frombytes("L",self.size,self.toBufferGray(8))
790 except:
791
792 self.pil = PIL.Image.fromstring("L",self.size,self.toBufferGray(8))
793 elif self.channels == 3:
794 try:
795 self.pil = PIL.Image.frombytes("RGB",self.size,self.toBufferRGB(8))
796 except:
797 self.pil = PIL.Image.fromstring("RGB",self.size,self.toBufferRGB(8))
798 else:
799 raise NotImplementedError("Cannot convert image from type: %s"%self.type)
800
802 '''
803 Create a color opencv representation of the image.
804 TODO: The OpenCV databuffer seems to be automatically swapped from RGB to BGR. This is counter intuitive.
805 '''
806
807 w,h = self.size
808
809 if self.channels == 1:
810 gray = cv.CreateImage((w,h),cv.IPL_DEPTH_8U,1)
811 cv.SetData(gray,self.toBufferGray(8))
812 self.opencv = gray
813
814 elif self.channels == 3:
815 rgb = cv.CreateImage((w,h),cv.IPL_DEPTH_8U,3)
816 bgr = cv.CreateImage((w,h),cv.IPL_DEPTH_8U,3)
817 cv.SetData(rgb, self.toBufferRGB(8))
818
819 cv.CvtColor(rgb,bgr,cv.CV_RGB2BGR)
820 self.opencv=bgr
821 else:
822 raise NotImplementedError("Cannot convert image from type: %s"%self.type)
823
825 '''
826 Create a matrix version of the image compatible with OpenCV 2 (cv2) in BGR format.
827 '''
828 data_buffer = self.toBufferRGB(8)
829 self.opencv2 = cv2.cvtColor(numpy.frombuffer(data_buffer,numpy.uint8).reshape(self.height,self.width,3),cv2.COLOR_RGB2BGR)
830
831
833 '''
834 Create a matrix version of the image compatible with OpenCV 2 (cv2) in BGR format.
835 '''
836 data_buffer = self.toBufferGray(8)
837 self.opencv2bw = numpy.frombuffer(data_buffer,numpy.uint8).reshape(self.height,self.width)
838
839
840
842 '''
843 @param depth: Use 8, 32, or 64, to specify the bit depth of the pixels.
844 @return: the image data as a binary python string.
845 '''
846 image_buffer = None
847 if self.type == TYPE_PIL:
848
849 pil = self.pil
850 if pil.mode != 'L':
851 pil = pil.convert('L')
852 try:
853
854 image_buffer = pil.tobytes()
855 except:
856
857 image_buffer = pil.tostring()
858 elif self.type == TYPE_MATRIX_2D:
859
860 image_buffer = self.matrix2d.transpose().tostring()
861 elif self.type == TYPE_OPENCV2BW:
862
863 image_buffer = self.opencv2bw.tostring()
864 elif self.type == TYPE_OPENCV2:
865
866 tmp = cv2.cvtColor(self.opencv2, cv2.cv.CV_BGR2GRAY)
867 image_buffer = tmp.tostring()
868 elif self.type == TYPE_MATRIX_RGB:
869
870 mat = self.matrix3d
871 mat = LUMA[0]*mat[0] + LUMA[1]*mat[1] + LUMA[2]*mat[2]
872 image_buffer = mat.transpose().tostring()
873 elif self.type == TYPE_OPENCV:
874 if self.channels == 1:
875
876 image_buffer = self.opencv.tostring()
877 elif self.channels == 3:
878
879 w,h = self.width,self.height
880 gray = cv.CreateImage((w,h),cv.IPL_DEPTH_8U,1)
881 cv.CvtColor( self.opencv, gray, cv.CV_BGR2GRAY );
882 image_buffer = gray.tostring()
883 else:
884 raise TypeError("Operation not supported for image type.")
885 else:
886 raise TypeError("Operation not supported for image type.")
887
888
889 assert image_buffer
890
891
892 if depth == self.depth:
893 return image_buffer
894
895 else:
896 types = {8:numpy.uint8,32:numpy.float32,64:numpy.float64}
897
898
899 data = numpy.frombuffer(image_buffer,types[self.depth])
900
901 if depth==8:
902
903 max_value = data.max()
904 min_value = data.min()
905 data_range = max_value - min_value
906 if max_value <= 255 and min_value >= 0 and data_range >= 150:
907
908
909 pass
910 else:
911
912 if max_value == min_value:
913 max_value = min_value+1
914 data = (255.0/(max_value-min_value))*(data-min_value)
915
916 data = data.astype(types[depth])
917 return data.tostring()
918
919
921 '''
922 returns the image data as a binary python string.
923 '''
924 image_buffer = None
925 if self.type == TYPE_PIL:
926
927 pil = self.pil
928 if pil.mode != 'RGB':
929 pil = pil.convert('RGB')
930 try:
931
932 image_buffer = pil.tobytes()
933 except:
934
935 image_buffer = pil.tostring()
936 elif self.type == TYPE_MATRIX_2D:
937
938 mat = self.matrix2d.transpose()
939 tmp = np.zeros((3,self.height,self.width),numpy.float32)
940 tmp[0,:] = mat
941 tmp[1,:] = mat
942 tmp[2,:] = mat
943 image_buffer = mat.tostring()
944 elif self.type == TYPE_OPENCV2BW:
945
946 tmp = cv2.cvtColor(self.opencv2bw, cv2.cv.CV_GRAY2RGB)
947 image_buffer = tmp.tostring()
948 elif self.type == TYPE_OPENCV2:
949
950 tmp = cv2.cvtColor(self.opencv2, cv2.cv.CV_BGR2RGB)
951 image_buffer = tmp.tostring()
952 elif self.type == TYPE_MATRIX_RGB:
953
954 mat = self.matrix3d.transpose()
955 image_buffer = mat.tostring()
956 elif self.type == TYPE_OPENCV:
957
958 w,h = self.width,self.height
959 if self.channels == 3:
960 rgb = cv.CreateImage((w,h),cv.IPL_DEPTH_8U,3)
961 cv.CvtColor( self.opencv, rgb, cv.CV_BGR2RGB );
962 image_buffer = rgb.tostring()
963 elif self.channels == 1:
964 rgb = cv.CreateImage((w,h),cv.IPL_DEPTH_8U,3)
965 cv.CvtColor( self.opencv, rgb, cv.CV_GRAY2RGB );
966 image_buffer = rgb.tostring()
967 else:
968
969 raise TypeError("Operation not supported for image type.")
970 else:
971
972 raise TypeError("Operation not supported for image type.")
973
974 assert image_buffer
975
976
977 if depth == self.depth:
978 return image_buffer
979
980 else:
981 types = {8:numpy.uint8,32:numpy.float32,64:numpy.float64}
982
983
984 data = numpy.frombuffer(image_buffer,types[self.depth])
985
986 if depth==8:
987
988 max_value = data.max()
989 min_value = data.min()
990 data_range = max_value - min_value
991 if max_value <= 255 and min_value >= 0 and data_range >= 50:
992
993
994 pass
995 else:
996
997 if max_value == min_value:
998 max_value = min_value+1
999 data = (255.0/(max_value-min_value))*(data-min_value)
1000
1001 data = data.astype(types[depth])
1002 return data.tostring()
1003
1004
1006 '''
1007 returns the image data as a binary python string.
1008 TODO: Not yet implemented
1009 '''
1010
1012 ''' Returns a resized version of the image that fits in new_size but preserves the aspect ratio.
1013
1014 @param newSize: tuple (new_width, new_height)
1015 @returns: a new pyvision image that is the resized version of this image.
1016 '''
1017 w,h = self.size
1018 s1 = float(newSize[0])/w
1019 s2 = float(newSize[1])/h
1020 s = min(s1,s2)
1021 return self.scale(s)
1022
1023
1024 - def resize(self, newSize, **kwargs):
1025 ''' Returns a resized version of the image. This is a convenience function.
1026 For more control, look at the Affine class for arbitrary transformations.
1027 @param newSize: tuple (new_width, new_height)
1028 @returns: a new pyvision image that is the resized version of this image.
1029 '''
1030 tmp = self.asPIL()
1031 if newSize[0] < self.size[0] or newSize[1] < self.size[1]:
1032
1033 tmp = tmp.resize(newSize, ANTIALIAS)
1034 else:
1035
1036 tmp = tmp.resize(newSize, BICUBIC)
1037
1038 return pyvision.Image(tmp,**kwargs)
1039
1040 - def scale(self, scale):
1041 ''' Returns a scaled version of the image. This is a convenience function.
1042 For more control, look at the Affine class for arbitrary transformations.
1043 @param scale: a float indicating the scale factor
1044 @returns: a new pyvision image that is the scaled version of this image.
1045 '''
1046 assert scale > 0.0
1047 w,h = self.size
1048 new_size = (int(round(scale*w)),int(round(scale*h)))
1049 return self.resize(new_size)
1050
1052 '''
1053 Returns a new pv.Image which is a copy of (only) the current image.
1054 Other internal data stored by the current pv.Image will NOT be copied.
1055 This method uses cv.CloneImage so that the underlying image data will be
1056 disconnected from the original data. (Deep copy)
1057 '''
1058 imgdat = self.asOpenCV()
1059 imgdat2 = cv.CloneImage(imgdat)
1060 return pv.Image(imgdat2)
1061
1062 - def crop(self, rect, size=None, interpolation=None, return_affine=False):
1063 '''
1064 Crops an image to the given rectangle. Rectangle parameters are rounded to nearest
1065 integer values. High quality resampling. The default behavior is to use cv.GetSubRect
1066 to crop the image. This returns a slice the OpenCV image so modifying the resulting
1067 image data will also modify the data in this image. If a size is provide a new OpenCV
1068 image is created for that size and cv.Resize is used to copy the image data. If the
1069 bounds of the rectangle are outside the image, an affine transform (pv.AffineFromRect)
1070 is used to produce the croped image to properly handle regions outside the image.
1071 In this case the downsampling quality may not be as good. #
1072
1073 @param rect: a Rectangle defining the region to be cropped.
1074 @param size: a new size for the returned image. If None the result is not resized.
1075 @param interpolation: None = Autoselect or one of CV_INTER_AREA, CV_INTER_NN, CV_INTER_LINEAR, CV_INTER_BICUBIC
1076 @param return_affine: If True, also return an affine transform that can be used to transform points.
1077 @returns: a cropped version of the image or if return affine a tuple of (image,affine)
1078 @rtype: pv.Image
1079 '''
1080
1081
1082
1083 x,y,w,h = rect.asTuple()
1084
1085 x = int(np.round(x))
1086 y = int(np.round(y))
1087 w = int(np.round(w))
1088 h = int(np.round(h))
1089
1090
1091 if x < 0 or y < 0 or x+w > self.size[0] or y+h > self.size[1]:
1092 if size is None:
1093 size = (w,h)
1094
1095 affine = pv.AffineFromRect(pv.Rect(x,y,w,h),size)
1096 im = affine(self)
1097 if return_affine:
1098 return im,affine
1099 else:
1100 return im
1101
1102
1103 cvim = self.asOpenCV()
1104
1105
1106 subim = cv.GetSubRect(cvim,(x,y,w,h))
1107
1108 affine = pv.AffineTranslate(-x,-y,(w,h))
1109
1110 if size is None:
1111 size = (w,h)
1112
1113
1114 new_image = cv.CreateImage(size,cvim.depth,cvim.nChannels)
1115 if interpolation is None:
1116
1117 if size[0] < w or size[1] < y:
1118
1119 interpolation = cv.CV_INTER_AREA
1120 else:
1121
1122 interpolation = cv.CV_INTER_CUBIC
1123
1124
1125 cv.Resize(subim,new_image,interpolation)
1126
1127 affine = pv.AffineNonUniformScale(float(size[0])/w,float(size[1])/h,size)*affine
1128
1129
1130 if return_affine:
1131 return pv.Image(new_image),affine
1132 else:
1133 return pv.Image(new_image)
1134
1135
1136 - def save(self,filename,annotations=False):
1137 '''
1138 Save the image to a file. This is performed by converting to PIL and
1139 then saving to a file based on on the extension.
1140 '''
1141 if filename[-4:] == ".raw":
1142
1143 raise NotImplementedError("Cannot save as a matrix")
1144
1145
1146
1147 else:
1148 if annotations:
1149 self.asAnnotated().save(filename)
1150 else:
1151 self.asPIL().save(filename)
1152
1153
1154 - def show(self, window=None, pos=None, delay=0, size=None):
1155 '''
1156 Displays the annotated version of the image using OpenCV highgui
1157 @param window: the name of the highgui window to use, if one already exists by this name,
1158 or it will create a new highgui window with this name.
1159 @param pos: if a new window is being created, the (x,y) coordinate for the new window
1160 @param delay: A delay in milliseconds to wait for keyboard input (passed to cv.WaitKey).
1161 0 delays indefinitely, 30 is good for presenting a series of images like a video.
1162 For performance reasons, namely when using the same window to display successive
1163 frames of video, we don't want to tear-down and re-create the window each time.
1164 Thus the window frame will persist beyond the scope of the call to img.show(). The window
1165 will disappear after the program exits, or it can be destroyed with a call to cv.DestroyWindow.
1166 @param size: Optional output size for image, None=native size.
1167 @returns: the return value of the cv.WaitKey call.
1168 '''
1169 if window==None and pv.runningInNotebook() and 'pylab' in globals().keys():
1170
1171
1172 if size is None:
1173 size = self.size
1174
1175
1176 max_dim = max(size[0],size[1])
1177
1178 if max_dim > 800:
1179 scale = 800.0/max_dim
1180 size = (int(scale*size[0]),int(scale*size[1]))
1181
1182 w,h = size
1183
1184
1185
1186 IPython.core.pylabtools.figsize(1.25*w/72.0,1.25*h/72.0)
1187 pylab.figure()
1188 pylab.imshow(self.asAnnotated(),origin='upper',aspect='auto')
1189
1190 else:
1191
1192 if window is None:
1193 window = "PyVisionImage"
1194
1195
1196 cv.NamedWindow(window)
1197
1198
1199 if pos is not None:
1200 cv.MoveWindow(window, pos[0], pos[1])
1201
1202
1203 if size is not None:
1204 x = pyvision.Image(self.asAnnotated().resize(size) )
1205 else:
1206 x = pyvision.Image(self.asAnnotated())
1207
1208
1209 cv.ShowImage(window, x.asOpenCV() )
1210 key = cv.WaitKey(delay=delay)
1211 del x
1212 return key
1213
1214
1216
1217 return "pv.Image(w=%d,h=%d,c=%d,type=%s)"%(self.width,self.height,self.channels,self.type)
1218
1219
1221 '''
1222 Convert an OpenCV matrix to a numpy matrix.
1223
1224 Based on code from: http://opencv.willowgarage.com/wiki/PythonInterface
1225 '''
1226 depth2dtype = {
1227 cv.CV_8U: 'uint8',
1228 cv.CV_8S: 'int8',
1229 cv.CV_16U: 'uint16',
1230 cv.CV_16S: 'int16',
1231 cv.CV_32S: 'int32',
1232 cv.CV_32F: 'float32',
1233 cv.CV_64F: 'float64',
1234 }
1235
1236
1237 assert cvmat.channels == 1
1238 r = cvmat.rows
1239 c = cvmat.cols
1240
1241
1242 a = np.fromstring(
1243 cvmat.tostring(),
1244 dtype=depth2dtype[cvmat.type],
1245 count=r*c)
1246 a.shape = (r,c)
1247 return a
1248
1249
1251 '''
1252 Convert a numpy matrix to an OpenCV matrix.
1253
1254 Based on code from: http://opencv.willowgarage.com/wiki/PythonInterface
1255 '''
1256 dtype2depth = {
1257 'uint8': cv.CV_8U,
1258 'int8': cv.CV_8S,
1259 'uint16': cv.CV_16U,
1260 'int16': cv.CV_16S,
1261 'int32': cv.CV_32S,
1262 'float32': cv.CV_32F,
1263 'float64': cv.CV_64F,
1264 }
1265
1266
1267 assert len(a.shape) == 2
1268 r,c = a.shape
1269
1270
1271 cv_im = cv.CreateMat(r,c,dtype2depth[str(a.dtype)])
1272 cv.SetData(cv_im, a.tostring())
1273 return cv_im
1274