opencv-python画局部放⼤图
⽂章⽬录
这项功能的⽬的是为了⽅便使⽤opencv做图像标注⼯具。
为什么要画局部放⼤图?
在做图像数据标注时,很难⼀次就做到精准标注,经常需要微调才能达到⽐较好的标注效果。如果⽬标⽐较⼩,即使微调也难以做到精准,所以就需要另外⼀个窗⼝对标注区域进⾏局部放⼤以⽅便微调。
程序逻辑
本⽂中标注信息以矩形框作为⽰例,矩形框是图像标注中最常⽤到的⼀种标注信息形态。其他标注信息的设计逻辑雷同。
程序主要逻辑是:⿏标在任意窗⼝中做的操作都要同步映射到另外⼀个窗⼝。主要通过两个维度进⾏控制:1. ⿏标在主窗⼝还是在放⼤窗⼝;2. ⿏标的操作。其中主窗⼝指的是⽤来显⽰完整⼤图的窗⼝,放⼤窗⼝是⽤来显⽰局部放⼤区域的窗⼝。
⿏标在主窗⼝
⿏标移动:以当前点(current_pt)为中⼼显⽰⼀块放⼤区域,显⽰的范围由⼀个参数指定default_zoom_image_size。为了确保以current_pt为中⼼向四个⽅向均能取到default_zoom_image_size⼀半的值,current_pt的活动范围是⼤图减去⼀定的边缘区域,这个边缘区域就是default_zoom_image_size的⼀半。在活动范围内,current_pt等同于⿏标当前点,在边缘区
域,current_pt等于距离⿏标当前点最近的边缘点。
⿏标画矩形框:左键按下并拖动可以画矩形框,矩形框不会超出⼤图边界
删除矩形框:有两种情况会删除矩形框 —— 1. 左键画出来的矩形框⾯积为零;2. 右键点击
⿏标在放⼤窗⼝
⿏标移动:什么也不做。
⿏标画矩形框:左键按下并拖动可以画矩形框,矩形框可以超出放⼤窗⼝的边界,但是不会超出⼤图边界
删除矩形框:同主窗⼝的两种情况
⿏标左键或右键点击:可以起到选择current_pt的作⽤
总体来说,我们在主窗⼝做任何操作都可以⽐较随意,因为主窗⼝中除了矩形框外,图像本⾝不发⽣变化。⽽在放⼤窗⼝中做操作就需要相对保守⼀些,不然会⾮常乱,以⾄于⽆法操作。所以我们在主窗⼝中画矩形框时,放⼤窗⼝会随着矩形框不停改变;⽽我们在放⼤窗⼝中画矩形框时,放⼤窗⼝则保持不变(不然眼花缭乱)。
程序实例
把程序逻辑搞明⽩后下⾯代码就⽐较容易懂了。细节就不多说了,只说⼀个需要注意的⼤⽅向:我们需要为两个窗⼝各写⼀个⿏标回调函数。
先看⼀下单纯画矩形框的代码可能会有助于理解下⾯程序中的⼀些细节:
# -*- coding: utf-8 -*-
import cv2
class Rect(object):
def__init__(self, pt1=(0,0), pt2=(0,0)):
self.tl = pt1
self.br = pt2
def regularize(self):
"""
make sure tl = TopLeft point, br = BottomRight point
"""
tl =(min(self.tl[0], self.br[0]),min(self.tl[1], self.br[1]))
br =(max(self.tl[0], self.br[0]),max(self.tl[1], self.br[1]))
br =(max(self.tl[0], self.br[0]),max(self.tl[1], self.br[1]))
self.tl = tl
self.br = br
def get_center(self):
"""
get center point of Rect
"""
center_x =(self.tl[0]+ self.br[0])//2
center_y =(self.tl[1]+ self.br[1])//2
return center_x, center_y
def get_width(self):
"""
get width of Rect
"""
return abs(self.br[0]- self.tl[0])
def get_height(self):
"""
get height of Rect
"""
return abs(self.br[1]- self.tl[1])
def height_over_width(self):
"""
ratio of height over width
"""
_height()/ _width()
def get_area(self):
"""
get area of Rect
"""
_width()* _height()
class DrawZoom(object):
def__init__(self, image, color,
current_pt=(0,0),
default_zoom_image_size=(256,256)):
self.thickness =2
self.current_pt = current_pt
self.default_zoom_image_size = default_zoom_image_size
<_in_big_image = Rect()
<_in_zoom_image = Rect()
<_offset =(0,0)
self.is_drawing_big =False
self.is_drawing_zoom =False
self.big_image = py()
<_image =None
<_image_backup =None
<_zoom_image()
def get_image_height(self):
"""
get height of big image
"""
iginal_image.shape[0]
def get_image_width(self):
def get_image_width(self):
"""
get width of big image
"""
iginal_image.shape[1]
def get_margin_height(self):
"""
get height of margin. in the margin area of big image, coordinate of
current_pt does NOT change
"""
return self.default_zoom_image_size[0]//2
def get_margin_width(self):
"""
get width of margin
"""
return self.default_zoom_image_size[1]//2
def get_zoom_image(self, height_ratio_expand=0.2, width_ratio_expand=0.2): """
get zoom image for two cases: the rect exists or not.
height_ratio_expand and width_ratio_expand are used for expanding some        area of rect
"""
if ist_rect:
<_zoom_image_for_current_pt()
_in__area()>0:
rectangle函数opencv<_zoom_image_for_rect(height_ratio_expand,
width_ratio_expand)
def get_zoom_image_for_current_pt(self):
"""
get zoom image for current mouse point (when rect does not exist)
"""
# (x, y) is center coordinate
x =max(self.current_pt[0], _margin_width())
x =min(x, _image_width()- _margin_width())
y =max(self.current_pt[1], _margin_height())
y =min(y, _image_height()- _margin_height())
tl_x = x - _margin_width()
tl_y = y - _margin_height()
br_x = x + _margin_width()
br_y = y + _margin_height()
tl_x, tl_y, br_x, br_y = self.shrink_rect(tl_x, tl_y, br_x, br_y)
<_image = self.big_image[tl_y:br_y, tl_x:br_x]
<_image_backup = iginal_image[tl_y:br_y, tl_x:br_x]
<_offset =(tl_x, tl_y)
def get_zoom_image_for_rect(self, height_ratio_expand, width_ratio_expand): """
get zoom image when rect exists
"""
_in__area()==0:
return None
height_over_width_for_win_zoom = \
self.default_zoom_image_size[1]/ self.default_zoom_image_size[0]
center = _in__center()
_in_big_image.height_over_width()> \
height_over_width_for_win_zoom:
half_height =int(0.5*(1+ height_ratio_expand)*
<_in__height())
half_width =int(half_height / height_over_width_for_win_zoom) else:
half_width =int(0.5*(1+ width_ratio_expand)*
<_in__width())
half_height =int(half_width * height_over_width_for_win_zoom)        tl_x = center[0]- half_width
tl_y = center[1]- half_height
br_x = center[0]+ half_width
br_y = center[1]+ half_height
tl_x, tl_y, br_x, br_y = self.shrink_rect(tl_x, tl_y, br_x, br_y)
<_image = self.big_image[tl_y:br_y, tl_x:br_x]
<_image_backup = iginal_image[tl_y:br_y, tl_x:br_x]        _offset =(tl_x, tl_y)
@staticmethod
def clip(value, low, high):
"""
clip value between low and high
"""
output =max(value, low)
output =min(output, high)
return output
def shrink_point(self, x, y):
"""
shrink point (x, y) to inside big image
"""
x_shrink = self.clip(x,0, _image_width())
y_shrink = self.clip(y,0, _image_height())
return x_shrink, y_shrink
def shrink_rect(self, pt1_x, pt1_y, pt2_x, pt2_y):
"""
shrink rect to inside big image
"""
pt1_x = self.clip(pt1_x,0, _image_width())
pt1_y = self.clip(pt1_y,0, _image_height())
pt2_x = self.clip(pt2_x,0, _image_width())
pt2_y = self.clip(pt2_y,0, _image_height())
rect = Rect((pt1_x, pt1_y),(pt2_x, pt2_y))
tl_x, tl_y = rect.tl
br_x, br_y = rect.br
return tl_x, tl_y, br_x, br_y
def reset_big_image(self):
"""
reset big_image (for show) using original image
"""
self.big_image = py()
def reset_zoom_image(self):
"""
reset zoom_image (for show) using the zoom image backup
"""
<_image = _py()
def draw_rect_in_big_image(self):
"""
draw rect in big image
"""
<_in_big_image.tl, _in_big_image.br,
lor, thickness=self.thickness)
def draw_rect_in_zoom_image(self):
"""
draw rect in zoom image
"""
<_image,
<_in_zoom_image.tl, _in_zoom_image.br,
<_in_zoom_image.tl, _in_zoom_image.br,
lor, thickness=self.thickness)
def update_drawing_big(self):
"""
update drawing big image, and map the corresponding area to zoom image
"""
ist_rect:
self.draw_rect_in_big_image()
<_zoom_image()
def update_drawing_zoom(self):
"""
update drawing big and zoom image when drawing rect in zoom image
"""
ist_rect:
self.draw_rect_in_big_image()
self.draw_rect_in_zoom_image()
def onmouse_big_image(event, x, y, flags, draw_zoom):
if event == cv2.EVENT_LBUTTONDOWN:
# pick first point of rect
draw_zoom.is_drawing_big =True
_in_big_image.tl =(x, y)
ist_rect =True
elif draw_zoom.is_drawing_big and event == cv2.EVENT_MOUSEMOVE:
# pick second point of rect and draw current rect
_in_big_image.br = draw_zoom.shrink_point(x, y)
set_big_image()
draw_zoom.update_drawing_big()
elif event == cv2.EVENT_LBUTTONUP:
# finish drawing current rect
draw_zoom.is_drawing_big =False
_in_big_image.br = draw_zoom.shrink_point(x, y)
_in_ularize()
if _in__area()==0:
set_big_image()
_in_big_image = Rect()
ist_rect =False
draw_zoom.update_drawing_big()
elif(not draw_zoom.is_drawing_big)and event == cv2.EVENT_RBUTTONDOWN: # right button down to erase current rect
_in_big_image = Rect()
ist_rect =False
set_big_image()
draw_zoom.update_drawing_big()
else:
# default case: mouse move without rect
draw_zoom.current_pt =(x, y)
draw_zoom.update_drawing_big()
def onmouse_zoom_image(event, x, y, flags, draw_zoom):
if event == cv2.EVENT_LBUTTONDOWN:
# pick first point of rect
draw_zoom.is_drawing_zoom =True
_in_zoom_image.tl =(x, y)
_in_big_image.tl =(x + _offset[0],
y + _offset[1])
ist_rect =True
elif draw_zoom.is_drawing_zoom and event == cv2.EVENT_MOUSEMOVE:
# pick second point of rect and draw current rect
_in_zoom_image.br =(x, y)
_in_big_image.br = draw_zoom.shrink_point(
x + _offset[0], y + _offset[1])