forked from pdollar/toolbox
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathimRectRot.m
344 lines (310 loc) · 14.3 KB
/
imRectRot.m
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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
function [hPatch,api] = imRectRot( varargin )
% Create a draggable, resizable, rotatable rectangle or ellipse.
%
% The 'ellipse' param determines if the displayed object is a rectangle or
% ellipse. The object is identical in both cases, only the display changes.
% The created object may be queried or controlled programatically by using
% the returned api.
%
% The 'rotate' param determines overall behavior of the created object. If
% rotate=0, the resulting object (rect or ellipse) is axis aligned. In
% terms of the graphical interface it is identical to Matlab's imrect (can
% drag by clicking in interior or resize by clicking on edges), although it
% is much less cpu intensive. If rotate>0, the resulting object is
% rotatable. In addition to the interface for a non-rotatable object, four
% control points are present, one at the center of each edge. Three of
% these have color given by the 'color' flag, the last has color 'colorc'.
% The odd colored control point is used to display orientation. Dragging
% this control point or the one opposite changes orientation, dragging the
% remaining two resizes the object symmetrically. Finally, when creating a
% rotatable object, the first drag determines the major axes (height) of
% the object, with the width set to height*rotate (hence the rotate param
% also determines aspect ratio of newly created objects). Using this
% control scheme, an object can be naturally specified with two drags: the
% first is used to draw the major axes, the second to adjust the width.
%
% Position is represented by [x y w h theta], where [x,y] give the top/left
% corner of the rect PRIOR to rotation, [w,h] are the width and height, and
% theta is the angle in degrees. The final rect is given by first placing
% the rect at [x,y], then rotating it by theta around it's center. The
% advantage of this is that if theta=0, the first four elements are
% identical to the standard rect representation. The disadvantage is that
% [x,y] need not lie in the interior of the rect after rotation.
%
% USAGE
% [h,api] = imRectRot( varargin )
%
% INPUTS
% varargin - parameters (struct or name/value pairs)
% .hParent - [gca] object parent, typically an axes object
% .ellipse - [0] if true display ellipse otherwise display rectangle
% .rotate - [1] determines if object is axis aligned
% .pos - [] initial pos vector [x y w h theta] or [] or [x y]
% .lims - [] rectangle defining valid region for object placement
% .showLims - [0] draw rectangle representing lims
% .color - ['g'] color for the displayed object
% .colorc - ['b'] color for the control point displaying orientation
% .lw - [2] 'LineWidth' property for the displayed object
% .ls - ['-'] 'LineStyle' property for the displayed object
% .cross - [0] if 1 show diagonal, if 2 show cross
%
% OUTPUTS
% h - handle used to delete object
% api - interface allowing access to created object
% .getPos() - get position - returns 5 elt pos
% .setPos(pos) - set position (while respecting constraints)
% .setPosLock(b) - if lock set (b==true), object cannot change
% .setSizLock(b) - if lock set (b==true), object cannot change size
% .setDrgLock(b) - if lock set (b==true), object cannot be dragged
% .setSidLock(lk) - [4x1] set locks for each side (tp/rt/bt/lf)
% .setPosChnCb(f) - whenever pos changes (even slightly), calls f(pos)
% .setPosSetCb(f) - whenever pos finished changing, calls f(pos)
% .uistack(...) - calls 'uistack( [objectHandles], ... )', see uistack
% .setStyle(...) - set line style (ls), width (lw), color and colorc
%
% EXAMPLE - interactively place simple axis aligned rectangle
% figure(1), imshow peppers.png;
% [h,api]=imRectRot('rotate',0);
% api.setPosChnCb( @(pos) disp(num2str(pos)) );
%
% EXAMPLE - create rotatable ellpise that falls inside image
% figure(1); I=imread('cameraman.tif'); imshow(I); siz=size(I);
% [h,api]=imRectRot('pos',[60 60 40 40 45],'lims',[1 1 siz(1:2)-2 0],...
% 'showLims',1,'ellipse',1,'rotate',1,'color','w','colorc','y' );
% api.setPosSetCb( @(pos) disp(num2str(pos)) );
%
% See also IMRECT, RECTANGLE, PATCH
%
% Piotr's Computer Vision Matlab Toolbox Version 2.51
% Copyright 2014 Piotr Dollar. [pdollar-at-gmail.com]
% Licensed under the Simplified BSD License [see external/bsd.txt]
% global variables (shared by all functions below)
[hParent,pos,lims,rotate,crXs,crYs,posChnCb,posSetCb,posLock,sizLock,...
dgrLock,sidLock,hAx,hFig,hPatch,hBnds,hCntr,hEll] = deal([]);
% intitialize
intitialize( varargin{:} );
% create api
api = struct('getPos',@getPos, 'setPos',@setPos, 'uistack',@uistack1, ...
'setPosChnCb',@setPosChnCb, 'setPosSetCb',@setPosSetCb, ...
'setPosLock',@setPosLock, 'setSizLock',@setSizLock, ...
'setDrgLock',@setDrgLock, 'setSidLock',@setSidLock, ...
'setStyle',@setStyle );
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function intitialize( varargin )
% get default arguments
dfs={'hParent',gca, 'ellipse',0, 'rotate',1, 'pos',[], 'lims',[], ...
'showLims',0,'color','g','colorc','b','lw',2,'ls','-','cross',0};
[hParent,ellipse,rotate,pos,lims,showLims,color,colorc,lw,ls,cross]=...
getPrmDflt(varargin,dfs,1);
if(numel(pos)==4), pos=[pos(:)' 0]; end
if(numel(lims)==4), lims=[lims(:)' 0]; end
% get figure and axes handles
hAx = ancestor(hParent,'axes'); assert(~isempty(hAx));
hFig = ancestor(hAx,'figure'); assert(~isempty(hFig));
set( hFig, 'CurrentAxes', hAx );
% optionally display limits
if(showLims && ~isempty(lims)), [~,xs,ys]=rectToCorners(lims);
for j=1:4, ids=mod([j-1 j],4)+1; line(xs(ids),ys(ids)); end
end
% create objects for rectangle display/interface
if( ellipse ), linePrp={'color',[.7 .7 .7],'LineWidth',1};
else linePrp={'color',color,'LineWidth',lw,'LineStyle',ls}; end
if(isempty(pos)); vis='off'; else vis='on'; end;
assert(cross==0 || cross==1 || cross==2);
for i=1:4+cross, hBnds(i)=line(linePrp{:},'Visible',vis); end
% create transparent patch to capture clicks in object interior
hPatch=patch('FaceColor','none','EdgeColor','none');
% create objects for ellipse display/interface
if( ellipse )
ellPrp={'color',color,'LineWidth',lw,'LineStyle',ls};
ts=linspace(-180,180,50); crXs=cosd(ts); crYs=sind(ts);
hold on; hEll=plot(crXs,crYs,ellPrp{:},'Visible',vis); hold off;
end
% create objects for rotation display/interface
if( rotate )
circPrp={'Curvature',[1 1],'FaceColor',color,'EdgeColor',color};
for i=1:4, hCntr(i)=rectangle(circPrp{:},'Visible',vis); end
set(hCntr(1),'LineWidth',lw,'FaceColor',colorc);
end
% set callbacks on all objects
hs=[hPatch hBnds hCntr hEll];
set(hs,'ButtonDownFcn',{@btnDwn,0},'DeleteFcn',@deleteFcn);
% set or query initial position
posLock=0; sizLock=0; dgrLock=0; sidLock=[0 0 0 0];
if(length(pos)==5), setPos(pos,1); else
btnDwn([],[],-1); waitfor(hFig,'WindowButtonUpFcn','');
end
end
function [pc,rs,R] = rectInfo( pos0 )
% return rectangle center, radii, and rotation matrix
t=pos0(5); c=cosd(t); s=sind(t); R=[c -s; s c];
rs=pos0(3:4)/2; pc=pos0(1:2)+rs;
end
function [pts,xs,ys,pc] = rectToCorners( pos0 )
% return 4 rect corners in real world coords
[pc,rs,R]=rectInfo( pos0 );
x0=-rs(1); x1=rs(1); y0=-rs(2); y1=rs(2);
pts=[x0 y0; x1 y0; x1 y1; x0 y1]*R'+pc(ones(4,1),:);
xs=pts(:,1); ys=pts(:,2);
end
function pos1 = cornersToRect( pos0, x0, y0, x1, y1 )
% compute pos from 4 corners given in rect coords
[pc,~,R] = rectInfo( pos0 );
p0=[x0 y0]*R'+pc; p1=[x1 y1]*R'+pc;
pc=(p1+p0)/2; p0=(p0-pc)*R; p1=(p1-pc)*R;
pos1 = [pc+p0 p1-p0 pos0(5)];
end
function setPos( posNew, ignoreLims )
if(isempty(hBnds) || isempty(hPatch)); return; end; L=lims;
% if corners fall outside lims don't use posNew
if( ~isempty(L) && (nargin<2 || ~ignoreLims) )
if( posNew(5)==lims(5) )
% lims and posNew at same orient (do everything in rect coords)
[pc,rs,R]=rectInfo(posNew); p0=-rs; p1=rs;
pts=(rectToCorners(L)-pc(ones(4,1),:))*R;
L0=[pts(1,1) pts(1,2)]; L1=[pts(2,1) pts(3,2)];
p0=min(max(p0,L0),L1); p1=max(min(p1,L1),p0);
posNew=cornersToRect(posNew,p0(1),p0(2),p1(1),p1(2));
else
% lims and posNew at diff orient (do everything in lims coords)
[pc,rs,R]=rectInfo(L); p0=-rs; p1=rs;
p=(rectToCorners(posNew)-pc(ones(4,1),:))*R; xs=p(:,1); ys=p(:,2);
valid=[xs>=p0(1) xs<=p1(1) ys>=p0(2) ys<=p1(2)];
if(~all(valid(:))), return; end
end
end
% create invisible patch to captures key presses
pos=posNew; [~,xs,ys]=rectToCorners(pos);
vert=[xs ys ones(4,1)]; face=1:4;
set(hPatch,'Faces',face,'Vertices',vert);
% draw ellipse
if(~isempty(hEll)), [pc,rs]=rectInfo(posNew); th=pos(5);
xsEll = rs(1)*crXs*cosd(-th)+rs(2)*crYs*sind(-th)+pc(1);
ysEll = rs(2)*crYs*cosd(-th)-rs(1)*crXs*sind(-th)+pc(2);
set(hEll,'XData',xsEll,'YData',ysEll);
end
% draw rectangle boundaries and control circles
r=axisUnitsPerCentimeter();
r=max( r*.1, min(mean(pos(3:4))/10,r) );
for i=1:length(hBnds), ids=mod([i-1 i],4)+1;
if(i==5), ids=[1 3]; elseif(i==6), ids=[2 4]; end
set(hBnds(i),'Xdata',xs(ids),'Ydata',ys(ids));
if(rotate && i<=4), x=mean(xs(ids)); y=mean(ys(ids));
set(hCntr(i),'Position',[x-r y-r 2*r 2*r]);
end
end
end
function pnt = getCurPnt()
pnt=get(hAx,'CurrentPoint'); pnt=pnt([1 3]);
end
function d = axisUnitsPerCentimeter()
% approximage absolute axes size
units=get(hAx,'units'); set(hAx,'units','centimeters');
cm=get(hAx,'Position'); cm=max(cm(3:4));
set(hAx,'units',units);
% axes size in axes units
xLim=get(gca,'xLim'); yLim=get(gca,'yLim');
sizInner = max([xLim(2)-xLim(1),yLim(2)-yLim(1)]);
% units per centimeter
d = sizInner/cm;
end
function [side,cursor,flag] = getSide( pnt0 )
% get pnt in coordintates of center of rectangle
[pc,rs,R]=rectInfo(pos); pnt=(pnt0-pc)*R; th=pos(5);
% side(i) -1=lf/tp bnd; 0=interior; +1:rt/bt bnd; 2=center
t = axisUnitsPerCentimeter(); side=[0 0]; flag=0;
t = max( t*.1, min(mean(pos(3:4))/5,t) );
for i=1:2
ti = min(t,rs(i)/3);
if( abs(pnt(i))<ti && rotate ), side(i)=2; % near center
elseif( pnt(i)<-rs(i)+ti ), side(i)=-1; % near lf/tp boundary
elseif( pnt(i)>rs(i)-ti), side(i)=1; % near rt/bt boundary
end
end
% flag: -1: none, 0=resize; 1=drag; 2=rotate; 3=symmetric-resize
if((sidLock(4)&&side(1)==-1)||(sidLock(2)&&side(1)==1)), side(1)=0; end
if((sidLock(1)&&side(2)==-1)||(sidLock(3)&&side(2)==1)), side(2)=0; end
if(any(side==0) || all(side==2)), side(side==2)=0; end
if(side(1)==2), flag=2; cursor='crosshair'; return; end
if(sizLock), side=[0 0]; end
if(all(side==0) && dgrLock), cursor=''; flag=-1; return; end
if(all(side==0)), flag=1; side=pnt0; cursor='fleur'; return; end
if(side(2)==2), flag=3; side(2)=0; end
% select cursor based on position
cs={'bottom','botr','right','topr','top','topl','left','botl'};
a=mod(round((atan2(side(1),side(2))/pi-th/180)*4),8)+1; cursor=cs{a};
end
function btnDwn( h, evnt, flag ) %#ok<INUSL>
if(isempty(hBnds) || isempty(hPatch) || posLock), return; end
if( flag==-1 ) % create new rectangle
if(isempty(pos)), anchor=ginput(1); else anchor=pos(1:2); end
pos=[anchor 1 1 0]; setPos(pos);
set([hBnds hCntr hEll],'Visible','on');
if(rotate), cursor='crosshair'; flag=4;
else cursor='botr'; flag=0; end
else % resize, rotate or drag rectangle
pnt=getCurPnt(); [anchor,cursor,flag]=getSide( pnt );
if(flag==-1), return; end
end
set( hFig, 'Pointer', cursor );
set( hFig, 'WindowButtonMotionFcn',{@drag,flag,anchor,pos} );
set( hFig, 'WindowButtonUpFcn', @stopDrag );
end
function drag( h, evnt, flag, anchor, pos0 ) %#ok<INUSL>
if(isempty(hBnds) || isempty(hPatch)); return; end;
pnt = getCurPnt();
if( flag==1 ) % shift rectangle by del=pnt-anchor
setPos( [pos0(1:2)+(pnt-anchor) pos0(3:5)] );
elseif( flag==0 || flag==3 ) % resize in rectangle coordinate frame
[pc,rs,R]=rectInfo(pos0); p0=-rs; p1=rs; pnt=(pnt-pc)*R;
if( flag==3 ), p0(1)=-pnt(1); p1(1)=pnt(1); else
for i=1:2, if(anchor(i)>0), p1(i)=pnt(i); end; end
for i=1:2, if(anchor(i)<0), p0(i)=pnt(i); end; end
end; p0a=min(p0,p1); p1=max(p0,p1); p0=p0a;
setPos(cornersToRect(pos0,p0(1),p0(2),p1(1),p1(2)));
elseif( flag==2 || flag==4 ) % rotate rectangle
[~,xs,ys]=rectToCorners(pos0);
if(anchor(2)==-1), ids=[3 4]; else ids=[1 2]; end
p0=mean([xs(ids) ys(ids)]); pc=(p0+pnt)/2;
if(anchor(2)==-1), d=pnt-p0; else d=p0-pnt; end
h=sqrt(sum(d.^2)); th=atan2(d(1),-d(2))/pi*180;
if(flag==2), w=pos(3); else w=h*rotate; end
setPos([pc(1)-w/2 pc(2)-h/2 w h th]);
end
if(~isempty(posChnCb)); posChnCb(pos); end;
drawnow; % update display
end
function stopDrag( h, evnt ) %#ok<INUSD>
set( hFig, 'WindowButtonMotionFcn','');
set( hFig, 'WindowButtonUpFcn','');
set( hFig, 'Pointer', 'arrow' );
if(~isempty(posSetCb)); posSetCb(pos); end;
end
function deleteFcn( h, evnt ) %#ok<INUSD>
[posChnCb,posSetCb]=deal([]); hs=[hPatch hBnds hCntr hEll];
for i=1:length(hs), if(ishandle(hs(i))); delete(hs(i)); end; end
[hBnds,hPatch,hCntr,hEll]=deal([]);
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function pos0 = getPos(), pos0=pos; end
function setPosChnCb( f ), posChnCb=f; end
function setPosSetCb( f ), posSetCb=f; end
function setPosLock( b ), posLock=b; end
function setSizLock( b ), sizLock=b; end
function setDrgLock( b ), dgrLock=b; end
function setSidLock( lk )
sidLock=lk; vis={'off','on'};
for i=1:4, set(hCntr(i),'Visible',vis{2-lk(i)}); end
end
function uistack1( varargin )
if(isempty(hBnds) || isempty(hPatch)); return; end;
uistack( [hBnds hPatch hCntr hEll], varargin{:} );
end
function setStyle( ls, lw, color, colorc )
if(isempty(hEll)), h=hBnds; else h=hEll; end
set(h,'LineStyle',ls,'LineWidth',lw,'color',color);
if(~rotate), return; end; if(nargin<4), colorc='b'; end
set(hCntr,'FaceColor',color,'EdgeColor',color);
set(hCntr(1),'LineWidth',lw,'FaceColor',colorc);
end
end