Skip to content
Projects
Groups
Snippets
Help
Loading...
Sign in / Register
Toggle navigation
O
opencv
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Packages
Packages
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
submodule
opencv
Commits
c9ed7fee
Commit
c9ed7fee
authored
Aug 10, 2011
by
Vadim Pisarevsky
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fixed C++ implementation of Delaunay triangulation and moved it from sample to imgproc.
parent
edcfa64d
Show whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
899 additions
and
829 deletions
+899
-829
imgproc.hpp
modules/imgproc/include/opencv2/imgproc/imgproc.hpp
+92
-0
subdivision2d.cpp
modules/imgproc/src/subdivision2d.cpp
+781
-0
cv2.cpp
modules/python/src2/cv2.cpp
+2
-0
gen2.py
modules/python/src2/gen2.py
+1
-1
delaunay2.cpp
samples/cpp/delaunay2.cpp
+23
-828
No files found.
modules/imgproc/include/opencv2/imgproc/imgproc.hpp
View file @
c9ed7fee
...
...
@@ -1017,6 +1017,98 @@ CV_EXPORTS_W void fitLine( InputArray points, OutputArray line, int distType,
//! checks if the point is inside the contour. Optionally computes the signed distance from the point to the contour boundary
CV_EXPORTS_W
double
pointPolygonTest
(
InputArray
contour
,
Point2f
pt
,
bool
measureDist
);
class
CV_EXPORTS_W
Subdiv2D
{
public
:
enum
{
PTLOC_ERROR
=
-
2
,
PTLOC_OUTSIDE_RECT
=
-
1
,
PTLOC_INSIDE
=
0
,
PTLOC_VERTEX
=
1
,
PTLOC_ON_EDGE
=
2
};
enum
{
NEXT_AROUND_ORG
=
0x00
,
NEXT_AROUND_DST
=
0x22
,
PREV_AROUND_ORG
=
0x11
,
PREV_AROUND_DST
=
0x33
,
NEXT_AROUND_LEFT
=
0x13
,
NEXT_AROUND_RIGHT
=
0x31
,
PREV_AROUND_LEFT
=
0x20
,
PREV_AROUND_RIGHT
=
0x02
};
CV_WRAP
Subdiv2D
();
CV_WRAP
Subdiv2D
(
Rect
rect
);
CV_WRAP
void
initDelaunay
(
Rect
rect
);
CV_WRAP
int
insert
(
Point2f
pt
);
CV_WRAP
void
insert
(
const
vector
<
Point2f
>&
ptvec
);
CV_WRAP
int
locate
(
Point2f
pt
,
CV_OUT
int
&
edge
,
CV_OUT
int
&
vertex
);
CV_WRAP
int
findNearest
(
Point2f
pt
,
CV_OUT
Point2f
*
nearestPt
=
0
);
CV_WRAP
void
getEdgeList
(
CV_OUT
vector
<
Vec4f
>&
edgeList
)
const
;
CV_WRAP
void
getTriangleList
(
CV_OUT
vector
<
Vec6f
>&
triangleList
)
const
;
CV_WRAP
void
getVoronoiFacetList
(
const
vector
<
int
>&
idx
,
CV_OUT
vector
<
vector
<
Point2f
>
>&
facetList
,
CV_OUT
vector
<
Point2f
>&
facetCenters
);
CV_WRAP
Point2f
getVertex
(
int
vertex
,
CV_OUT
int
*
firstEdge
=
0
)
const
;
CV_WRAP
int
getEdge
(
int
edge
,
int
nextEdgeType
)
const
;
CV_WRAP
int
nextEdge
(
int
edge
)
const
;
CV_WRAP
int
rotateEdge
(
int
edge
,
int
rotate
)
const
;
CV_WRAP
int
symEdge
(
int
edge
)
const
;
CV_WRAP
int
edgeOrg
(
int
edge
,
CV_OUT
Point2f
*
orgpt
=
0
)
const
;
CV_WRAP
int
edgeDst
(
int
edge
,
CV_OUT
Point2f
*
dstpt
=
0
)
const
;
protected
:
int
newEdge
();
void
deleteEdge
(
int
edge
);
int
newPoint
(
Point2f
pt
,
bool
isvirtual
,
int
firstEdge
=
0
);
void
deletePoint
(
int
vtx
);
void
setEdgePoints
(
int
edge
,
int
orgPt
,
int
dstPt
);
void
splice
(
int
edgeA
,
int
edgeB
);
int
connectEdges
(
int
edgeA
,
int
edgeB
);
void
swapEdges
(
int
edge
);
int
isRightOf
(
Point2f
pt
,
int
edge
)
const
;
void
calcVoronoi
();
void
clearVoronoi
();
void
check
()
const
;
struct
CV_EXPORTS
Vertex
{
Vertex
();
Vertex
(
Point2f
pt
,
bool
_isvirtual
,
int
_firstEdge
=
0
);
bool
isvirtual
()
const
;
bool
isfree
()
const
;
int
firstEdge
;
int
type
;
Point2f
pt
;
};
struct
CV_EXPORTS
QuadEdge
{
QuadEdge
();
QuadEdge
(
int
edgeidx
);
bool
isfree
()
const
;
int
next
[
4
];
int
pt
[
4
];
};
vector
<
Vertex
>
vtx
;
vector
<
QuadEdge
>
qedges
;
int
freeQEdge
;
int
freePoint
;
bool
validGeometry
;
int
recentEdge
;
Point2f
topLeft
;
Point2f
bottomRight
;
};
}
// 2009-01-12, Xavier Delacour <xavier.delacour@gmail.com>
...
...
modules/imgproc/src/subdivision2d.cpp
View file @
c9ed7fee
...
...
@@ -710,4 +710,785 @@ cvFindNearestPoint2D( CvSubdiv2D* subdiv, CvPoint2D32f pt )
return
point
;
}
namespace
cv
{
int
Subdiv2D
::
nextEdge
(
int
edge
)
const
{
CV_DbgAssert
((
size_t
)(
edge
>>
2
)
<
qedges
.
size
());
return
qedges
[
edge
>>
2
].
next
[
edge
&
3
];
}
int
Subdiv2D
::
rotateEdge
(
int
edge
,
int
rotate
)
const
{
return
(
edge
&
~
3
)
+
((
edge
+
rotate
)
&
3
);
}
int
Subdiv2D
::
symEdge
(
int
edge
)
const
{
return
edge
^
2
;
}
int
Subdiv2D
::
getEdge
(
int
edge
,
int
nextEdgeType
)
const
{
CV_DbgAssert
((
size_t
)(
edge
>>
2
)
<
qedges
.
size
());
edge
=
qedges
[
edge
>>
2
].
next
[(
edge
+
nextEdgeType
)
&
3
];
return
(
edge
&
~
3
)
+
((
edge
+
(
nextEdgeType
>>
4
))
&
3
);
}
int
Subdiv2D
::
edgeOrg
(
int
edge
,
CV_OUT
Point2f
*
orgpt
)
const
{
CV_DbgAssert
((
size_t
)(
edge
>>
2
)
<
qedges
.
size
());
int
vidx
=
qedges
[
edge
>>
2
].
pt
[
edge
&
3
];
if
(
orgpt
)
{
CV_DbgAssert
((
size_t
)
vidx
<
vtx
.
size
());
*
orgpt
=
vtx
[
vidx
].
pt
;
}
return
vidx
;
}
int
Subdiv2D
::
edgeDst
(
int
edge
,
CV_OUT
Point2f
*
dstpt
)
const
{
CV_DbgAssert
((
size_t
)(
edge
>>
2
)
<
qedges
.
size
());
int
vidx
=
qedges
[
edge
>>
2
].
pt
[(
edge
+
2
)
&
3
];
if
(
dstpt
)
{
CV_DbgAssert
((
size_t
)
vidx
<
vtx
.
size
());
*
dstpt
=
vtx
[
vidx
].
pt
;
}
return
vidx
;
}
Point2f
Subdiv2D
::
getVertex
(
int
vertex
,
CV_OUT
int
*
firstEdge
)
const
{
CV_DbgAssert
((
size_t
)
vertex
<
vtx
.
size
());
if
(
firstEdge
)
*
firstEdge
=
vtx
[
vertex
].
firstEdge
;
return
vtx
[
vertex
].
pt
;
}
Subdiv2D
::
Subdiv2D
()
{
validGeometry
=
false
;
freeQEdge
=
0
;
freePoint
=
0
;
recentEdge
=
0
;
}
Subdiv2D
::
Subdiv2D
(
Rect
rect
)
{
validGeometry
=
false
;
freeQEdge
=
0
;
freePoint
=
0
;
recentEdge
=
0
;
initDelaunay
(
rect
);
}
Subdiv2D
::
QuadEdge
::
QuadEdge
()
{
next
[
0
]
=
next
[
1
]
=
next
[
2
]
=
next
[
3
]
=
0
;
pt
[
0
]
=
pt
[
1
]
=
pt
[
2
]
=
pt
[
3
]
=
0
;
}
Subdiv2D
::
QuadEdge
::
QuadEdge
(
int
edgeidx
)
{
CV_DbgAssert
((
edgeidx
&
3
)
==
0
);
next
[
0
]
=
edgeidx
;
next
[
1
]
=
edgeidx
+
3
;
next
[
2
]
=
edgeidx
+
2
;
next
[
3
]
=
edgeidx
+
1
;
pt
[
0
]
=
pt
[
1
]
=
pt
[
2
]
=
pt
[
3
]
=
0
;
}
bool
Subdiv2D
::
QuadEdge
::
isfree
()
const
{
return
next
[
0
]
<=
0
;
}
Subdiv2D
::
Vertex
::
Vertex
()
{
firstEdge
=
0
;
type
=
-
1
;
}
Subdiv2D
::
Vertex
::
Vertex
(
Point2f
_pt
,
bool
_isvirtual
,
int
_firstEdge
)
{
firstEdge
=
_firstEdge
;
type
=
(
int
)
_isvirtual
;
pt
=
_pt
;
}
bool
Subdiv2D
::
Vertex
::
isvirtual
()
const
{
return
type
>
0
;
}
bool
Subdiv2D
::
Vertex
::
isfree
()
const
{
return
type
<
0
;
}
void
Subdiv2D
::
splice
(
int
edgeA
,
int
edgeB
)
{
int
&
a_next
=
qedges
[
edgeA
>>
2
].
next
[
edgeA
&
3
];
int
&
b_next
=
qedges
[
edgeB
>>
2
].
next
[
edgeB
&
3
];
int
a_rot
=
rotateEdge
(
a_next
,
1
);
int
b_rot
=
rotateEdge
(
b_next
,
1
);
int
&
a_rot_next
=
qedges
[
a_rot
>>
2
].
next
[
a_rot
&
3
];
int
&
b_rot_next
=
qedges
[
b_rot
>>
2
].
next
[
b_rot
&
3
];
std
::
swap
(
a_next
,
b_next
);
std
::
swap
(
a_rot_next
,
b_rot_next
);
}
void
Subdiv2D
::
setEdgePoints
(
int
edge
,
int
orgPt
,
int
dstPt
)
{
qedges
[
edge
>>
2
].
pt
[
edge
&
3
]
=
orgPt
;
qedges
[
edge
>>
2
].
pt
[(
edge
+
2
)
&
3
]
=
dstPt
;
vtx
[
orgPt
].
firstEdge
=
edge
;
vtx
[
dstPt
].
firstEdge
=
edge
^
2
;
}
int
Subdiv2D
::
connectEdges
(
int
edgeA
,
int
edgeB
)
{
int
edge
=
newEdge
();
splice
(
edge
,
getEdge
(
edgeA
,
NEXT_AROUND_LEFT
));
splice
(
symEdge
(
edge
),
edgeB
);
setEdgePoints
(
edge
,
edgeDst
(
edgeA
),
edgeOrg
(
edgeB
));
return
edge
;
}
void
Subdiv2D
::
swapEdges
(
int
edge
)
{
int
sedge
=
symEdge
(
edge
);
int
a
=
getEdge
(
edge
,
PREV_AROUND_ORG
);
int
b
=
getEdge
(
sedge
,
PREV_AROUND_ORG
);
splice
(
edge
,
a
);
splice
(
sedge
,
b
);
setEdgePoints
(
edge
,
edgeDst
(
a
),
edgeDst
(
b
));
splice
(
edge
,
getEdge
(
a
,
NEXT_AROUND_LEFT
));
splice
(
sedge
,
getEdge
(
b
,
NEXT_AROUND_LEFT
));
}
int
Subdiv2D
::
isRightOf
(
Point2f
pt
,
int
edge
)
const
{
Point2f
org
,
dst
;
edgeOrg
(
edge
,
&
org
);
edgeDst
(
edge
,
&
dst
);
double
cw_area
=
cvTriangleArea
(
pt
,
dst
,
org
);
return
(
cw_area
>
0
)
-
(
cw_area
<
0
);
}
int
Subdiv2D
::
newEdge
()
{
if
(
freeQEdge
<=
0
)
{
qedges
.
push_back
(
QuadEdge
());
freeQEdge
=
(
int
)(
qedges
.
size
()
-
1
);
}
int
edge
=
freeQEdge
*
4
;
freeQEdge
=
qedges
[
edge
>>
2
].
next
[
1
];
qedges
[
edge
>>
2
]
=
QuadEdge
(
edge
);
return
edge
;
}
void
Subdiv2D
::
deleteEdge
(
int
edge
)
{
CV_DbgAssert
((
size_t
)(
edge
>>
2
)
<
(
size_t
)
qedges
.
size
());
splice
(
edge
,
getEdge
(
edge
,
PREV_AROUND_ORG
)
);
int
sedge
=
symEdge
(
edge
);
splice
(
sedge
,
getEdge
(
sedge
,
PREV_AROUND_ORG
)
);
edge
>>=
2
;
qedges
[
edge
].
next
[
0
]
=
0
;
qedges
[
edge
].
next
[
1
]
=
freeQEdge
;
freeQEdge
=
edge
;
}
int
Subdiv2D
::
newPoint
(
Point2f
pt
,
bool
isvirtual
,
int
firstEdge
)
{
if
(
freePoint
==
0
)
{
vtx
.
push_back
(
Vertex
());
freePoint
=
(
int
)(
vtx
.
size
()
-
1
);
}
int
vidx
=
freePoint
;
freePoint
=
vtx
[
vidx
].
firstEdge
;
vtx
[
vidx
]
=
Vertex
(
pt
,
isvirtual
,
firstEdge
);
return
vidx
;
}
void
Subdiv2D
::
deletePoint
(
int
vidx
)
{
CV_DbgAssert
(
(
size_t
)
vidx
<
vtx
.
size
()
);
vtx
[
vidx
].
firstEdge
=
freePoint
;
vtx
[
vidx
].
type
=
-
1
;
freePoint
=
vidx
;
}
int
Subdiv2D
::
locate
(
Point2f
pt
,
int
&
_edge
,
int
&
_vertex
)
{
int
vertex
=
0
;
int
i
,
maxEdges
=
(
int
)(
qedges
.
size
()
*
4
);
if
(
qedges
.
size
()
<
(
size_t
)
4
)
CV_Error
(
CV_StsError
,
"Subdivision is empty"
);
if
(
pt
.
x
<
topLeft
.
x
||
pt
.
y
<
topLeft
.
y
||
pt
.
x
>=
bottomRight
.
x
||
pt
.
y
>=
bottomRight
.
y
)
CV_Error
(
CV_StsOutOfRange
,
""
);
int
edge
=
recentEdge
;
CV_Assert
(
edge
>
0
);
int
location
=
PTLOC_ERROR
;
int
right_of_curr
=
isRightOf
(
pt
,
edge
);
if
(
right_of_curr
>
0
)
{
edge
=
symEdge
(
edge
);
right_of_curr
=
-
right_of_curr
;
}
for
(
i
=
0
;
i
<
maxEdges
;
i
++
)
{
int
onext_edge
=
nextEdge
(
edge
);
int
dprev_edge
=
getEdge
(
edge
,
PREV_AROUND_DST
);
int
right_of_onext
=
isRightOf
(
pt
,
onext_edge
);
int
right_of_dprev
=
isRightOf
(
pt
,
dprev_edge
);
if
(
right_of_dprev
>
0
)
{
if
(
right_of_onext
>
0
||
(
right_of_onext
==
0
&&
right_of_curr
==
0
)
)
{
location
=
PTLOC_INSIDE
;
break
;
}
else
{
right_of_curr
=
right_of_onext
;
edge
=
onext_edge
;
}
}
else
{
if
(
right_of_onext
>
0
)
{
if
(
right_of_dprev
==
0
&&
right_of_curr
==
0
)
{
location
=
PTLOC_INSIDE
;
break
;
}
else
{
right_of_curr
=
right_of_dprev
;
edge
=
dprev_edge
;
}
}
else
if
(
right_of_curr
==
0
&&
isRightOf
(
vtx
[
edgeDst
(
onext_edge
)].
pt
,
edge
)
>=
0
)
{
edge
=
symEdge
(
edge
);
}
else
{
right_of_curr
=
right_of_onext
;
edge
=
onext_edge
;
}
}
}
recentEdge
=
edge
;
if
(
location
==
PTLOC_INSIDE
)
{
Point2f
org_pt
,
dst_pt
;
edgeOrg
(
edge
,
&
org_pt
);
edgeDst
(
edge
,
&
dst_pt
);
double
t1
=
fabs
(
pt
.
x
-
org_pt
.
x
);
t1
+=
fabs
(
pt
.
y
-
org_pt
.
y
);
double
t2
=
fabs
(
pt
.
x
-
dst_pt
.
x
);
t2
+=
fabs
(
pt
.
y
-
dst_pt
.
y
);
double
t3
=
fabs
(
org_pt
.
x
-
dst_pt
.
x
);
t3
+=
fabs
(
org_pt
.
y
-
dst_pt
.
y
);
if
(
t1
<
FLT_EPSILON
)
{
location
=
PTLOC_VERTEX
;
vertex
=
edgeOrg
(
edge
);
edge
=
0
;
}
else
if
(
t2
<
FLT_EPSILON
)
{
location
=
PTLOC_VERTEX
;
vertex
=
edgeDst
(
edge
);
edge
=
0
;
}
else
if
(
(
t1
<
t3
||
t2
<
t3
)
&&
fabs
(
cvTriangleArea
(
pt
,
org_pt
,
dst_pt
))
<
FLT_EPSILON
)
{
location
=
PTLOC_ON_EDGE
;
vertex
=
0
;
}
}
if
(
location
==
PTLOC_ERROR
)
{
edge
=
0
;
vertex
=
0
;
}
_edge
=
edge
;
_vertex
=
vertex
;
return
location
;
}
inline
int
isPtInCircle3
(
Point2f
pt
,
Point2f
a
,
Point2f
b
,
Point2f
c
)
{
const
double
eps
=
FLT_EPSILON
*
0.125
;
double
val
=
((
double
)
a
.
x
*
a
.
x
+
(
double
)
a
.
y
*
a
.
y
)
*
cvTriangleArea
(
b
,
c
,
pt
);
val
-=
((
double
)
b
.
x
*
b
.
x
+
(
double
)
b
.
y
*
b
.
y
)
*
cvTriangleArea
(
a
,
c
,
pt
);
val
+=
((
double
)
c
.
x
*
c
.
x
+
(
double
)
c
.
y
*
c
.
y
)
*
cvTriangleArea
(
a
,
b
,
pt
);
val
-=
((
double
)
pt
.
x
*
pt
.
x
+
(
double
)
pt
.
y
*
pt
.
y
)
*
cvTriangleArea
(
a
,
b
,
c
);
return
val
>
eps
?
1
:
val
<
-
eps
?
-
1
:
0
;
}
int
Subdiv2D
::
insert
(
Point2f
pt
)
{
int
curr_point
=
0
,
curr_edge
=
0
,
deleted_edge
=
0
;
int
location
=
locate
(
pt
,
curr_edge
,
curr_point
);
if
(
location
==
PTLOC_ERROR
)
CV_Error
(
CV_StsBadSize
,
""
);
if
(
location
==
PTLOC_OUTSIDE_RECT
)
CV_Error
(
CV_StsOutOfRange
,
""
);
if
(
location
==
PTLOC_VERTEX
)
return
curr_point
;
if
(
location
==
PTLOC_ON_EDGE
)
{
deleted_edge
=
curr_edge
;
recentEdge
=
curr_edge
=
getEdge
(
curr_edge
,
PREV_AROUND_ORG
);
deleteEdge
(
deleted_edge
);
}
else
if
(
location
==
PTLOC_INSIDE
)
;
else
CV_Error_
(
CV_StsError
,
(
"Subdiv2D::locate returned invalid location = %d"
,
location
)
);
assert
(
curr_edge
!=
0
);
validGeometry
=
false
;
curr_point
=
newPoint
(
pt
,
false
);
int
base_edge
=
newEdge
();
int
first_point
=
edgeOrg
(
curr_edge
);
setEdgePoints
(
base_edge
,
first_point
,
curr_point
);
splice
(
base_edge
,
curr_edge
);
do
{
base_edge
=
connectEdges
(
curr_edge
,
symEdge
(
base_edge
)
);
curr_edge
=
getEdge
(
base_edge
,
PREV_AROUND_ORG
);
}
while
(
edgeDst
(
curr_edge
)
!=
first_point
);
curr_edge
=
getEdge
(
base_edge
,
PREV_AROUND_ORG
);
int
i
,
max_edges
=
(
int
)(
qedges
.
size
()
*
4
);
for
(
i
=
0
;
i
<
max_edges
;
i
++
)
{
int
temp_dst
=
0
,
curr_org
=
0
,
curr_dst
=
0
;
int
temp_edge
=
getEdge
(
curr_edge
,
PREV_AROUND_ORG
);
temp_dst
=
edgeDst
(
temp_edge
);
curr_org
=
edgeOrg
(
curr_edge
);
curr_dst
=
edgeDst
(
curr_edge
);
if
(
isRightOf
(
vtx
[
temp_dst
].
pt
,
curr_edge
)
>
0
&&
isPtInCircle3
(
vtx
[
curr_org
].
pt
,
vtx
[
temp_dst
].
pt
,
vtx
[
curr_dst
].
pt
,
vtx
[
curr_point
].
pt
)
<
0
)
{
swapEdges
(
curr_edge
);
curr_edge
=
getEdge
(
curr_edge
,
PREV_AROUND_ORG
);
}
else
if
(
curr_org
==
first_point
)
break
;
else
curr_edge
=
getEdge
(
nextEdge
(
curr_edge
),
PREV_AROUND_LEFT
);
}
return
curr_point
;
}
void
Subdiv2D
::
insert
(
const
vector
<
Point2f
>&
ptvec
)
{
for
(
size_t
i
=
0
;
i
<
ptvec
.
size
();
i
++
)
insert
(
ptvec
[
i
]);
}
void
Subdiv2D
::
initDelaunay
(
Rect
rect
)
{
float
big_coord
=
3.
f
*
MAX
(
rect
.
width
,
rect
.
height
);
float
rx
=
(
float
)
rect
.
x
;
float
ry
=
(
float
)
rect
.
y
;
vtx
.
clear
();
qedges
.
clear
();
recentEdge
=
0
;
validGeometry
=
false
;
topLeft
=
Point2f
(
rx
,
ry
);
bottomRight
=
Point2f
(
rx
+
rect
.
width
,
ry
+
rect
.
height
);
Point2f
ppA
(
rx
+
big_coord
,
ry
);
Point2f
ppB
(
rx
,
ry
+
big_coord
);
Point2f
ppC
(
rx
-
big_coord
,
ry
-
big_coord
);
vtx
.
push_back
(
Vertex
());
qedges
.
push_back
(
QuadEdge
());
freeQEdge
=
0
;
freePoint
=
0
;
int
pA
=
newPoint
(
ppA
,
false
);
int
pB
=
newPoint
(
ppB
,
false
);
int
pC
=
newPoint
(
ppC
,
false
);
int
edge_AB
=
newEdge
();
int
edge_BC
=
newEdge
();
int
edge_CA
=
newEdge
();
setEdgePoints
(
edge_AB
,
pA
,
pB
);
setEdgePoints
(
edge_BC
,
pB
,
pC
);
setEdgePoints
(
edge_CA
,
pC
,
pA
);
splice
(
edge_AB
,
symEdge
(
edge_CA
));
splice
(
edge_BC
,
symEdge
(
edge_AB
));
splice
(
edge_CA
,
symEdge
(
edge_BC
));
recentEdge
=
edge_AB
;
}
void
Subdiv2D
::
clearVoronoi
()
{
size_t
i
,
total
=
qedges
.
size
();
for
(
i
=
0
;
i
<
total
;
i
++
)
qedges
[
i
].
pt
[
1
]
=
qedges
[
i
].
pt
[
3
]
=
0
;
total
=
vtx
.
size
();
for
(
i
=
0
;
i
<
total
;
i
++
)
{
if
(
vtx
[
i
].
isvirtual
()
)
deletePoint
((
int
)
i
);
}
validGeometry
=
false
;
}
static
Point2f
computeVoronoiPoint
(
Point2f
org0
,
Point2f
dst0
,
Point2f
org1
,
Point2f
dst1
)
{
double
a0
=
dst0
.
x
-
org0
.
x
;
double
b0
=
dst0
.
y
-
org0
.
y
;
double
c0
=
-
0.5
*
(
a0
*
(
dst0
.
x
+
org0
.
x
)
+
b0
*
(
dst0
.
y
+
org0
.
y
));
double
a1
=
dst1
.
x
-
org1
.
x
;
double
b1
=
dst1
.
y
-
org1
.
y
;
double
c1
=
-
0.5
*
(
a1
*
(
dst1
.
x
+
org1
.
x
)
+
b1
*
(
dst1
.
y
+
org1
.
y
));
double
det
=
a0
*
b1
-
a1
*
b0
;
if
(
det
!=
0
)
{
det
=
1.
/
det
;
return
Point2f
((
float
)
((
b0
*
c1
-
b1
*
c0
)
*
det
),
(
float
)
((
a1
*
c0
-
a0
*
c1
)
*
det
));
}
return
Point2f
(
FLT_MAX
,
FLT_MAX
);
}
void
Subdiv2D
::
calcVoronoi
()
{
// check if it is already calculated
if
(
validGeometry
)
return
;
clearVoronoi
();
int
i
,
total
=
(
int
)
qedges
.
size
();
// loop through all quad-edges, except for the first 3 (#1, #2, #3 - 0 is reserved for "NULL" pointer)
for
(
i
=
4
;
i
<
total
;
i
++
)
{
QuadEdge
&
quadedge
=
qedges
[
i
];
if
(
quadedge
.
isfree
()
)
continue
;
int
edge0
=
(
int
)(
i
*
4
);
Point2f
org0
,
dst0
,
org1
,
dst1
;
if
(
!
quadedge
.
pt
[
3
]
)
{
int
edge1
=
getEdge
(
edge0
,
NEXT_AROUND_LEFT
);
int
edge2
=
getEdge
(
edge1
,
NEXT_AROUND_LEFT
);
edgeOrg
(
edge0
,
&
org0
);
edgeDst
(
edge0
,
&
dst0
);
edgeOrg
(
edge1
,
&
org1
);
edgeDst
(
edge1
,
&
dst1
);
Point2f
virt_point
=
computeVoronoiPoint
(
org0
,
dst0
,
org1
,
dst1
);
if
(
fabs
(
virt_point
.
x
)
<
FLT_MAX
*
0.5
&&
fabs
(
virt_point
.
y
)
<
FLT_MAX
*
0.5
)
{
quadedge
.
pt
[
3
]
=
qedges
[
edge1
>>
2
].
pt
[
3
-
(
edge1
&
2
)]
=
qedges
[
edge2
>>
2
].
pt
[
3
-
(
edge2
&
2
)]
=
newPoint
(
virt_point
,
true
);
}
}
if
(
!
quadedge
.
pt
[
1
]
)
{
int
edge1
=
getEdge
(
edge0
,
NEXT_AROUND_RIGHT
);
int
edge2
=
getEdge
(
edge1
,
NEXT_AROUND_RIGHT
);
edgeOrg
(
edge0
,
&
org0
);
edgeDst
(
edge0
,
&
dst0
);
edgeOrg
(
edge1
,
&
org1
);
edgeDst
(
edge1
,
&
dst1
);
Point2f
virt_point
=
computeVoronoiPoint
(
org0
,
dst0
,
org1
,
dst1
);
if
(
fabs
(
virt_point
.
x
)
<
FLT_MAX
*
0.5
&&
fabs
(
virt_point
.
y
)
<
FLT_MAX
*
0.5
)
{
quadedge
.
pt
[
1
]
=
qedges
[
edge1
>>
2
].
pt
[
1
+
(
edge1
&
2
)]
=
qedges
[
edge2
>>
2
].
pt
[
1
+
(
edge2
&
2
)]
=
newPoint
(
virt_point
,
true
);
}
}
}
validGeometry
=
true
;
}
static
int
isRightOf2
(
const
Point2f
&
pt
,
const
Point2f
&
org
,
const
Point2f
&
diff
)
{
double
cw_area
=
((
double
)
org
.
x
-
pt
.
x
)
*
diff
.
y
-
((
double
)
org
.
y
-
pt
.
y
)
*
diff
.
x
;
return
(
cw_area
>
0
)
-
(
cw_area
<
0
);
}
int
Subdiv2D
::
findNearest
(
Point2f
pt
,
Point2f
*
nearestPt
)
{
if
(
!
validGeometry
)
calcVoronoi
();
int
vertex
=
0
,
edge
=
0
;
int
loc
=
locate
(
pt
,
edge
,
vertex
);
if
(
loc
!=
PTLOC_ON_EDGE
&&
loc
!=
PTLOC_INSIDE
)
return
vertex
;
vertex
=
0
;
Point2f
start
;
edgeOrg
(
edge
,
&
start
);
Point2f
diff
=
pt
-
start
;
edge
=
rotateEdge
(
edge
,
1
);
int
i
,
total
=
(
int
)
vtx
.
size
();
for
(
i
=
0
;
i
<
total
;
i
++
)
{
Point2f
t
;
for
(;;)
{
CV_Assert
(
edgeDst
(
edge
,
&
t
)
>
0
);
if
(
isRightOf2
(
t
,
start
,
diff
)
>=
0
)
break
;
edge
=
getEdge
(
edge
,
NEXT_AROUND_LEFT
);
}
for
(;;)
{
CV_Assert
(
edgeOrg
(
edge
,
&
t
)
>
0
);
if
(
isRightOf2
(
t
,
start
,
diff
)
<
0
)
break
;
edge
=
getEdge
(
edge
,
PREV_AROUND_LEFT
);
}
Point2f
tempDiff
;
edgeDst
(
edge
,
&
tempDiff
);
edgeOrg
(
edge
,
&
t
);
tempDiff
-=
t
;
if
(
isRightOf2
(
pt
,
t
,
tempDiff
)
>=
0
)
{
vertex
=
edgeOrg
(
rotateEdge
(
edge
,
3
));
break
;
}
edge
=
symEdge
(
edge
);
}
if
(
nearestPt
&&
vertex
>
0
)
*
nearestPt
=
vtx
[
vertex
].
pt
;
return
vertex
;
}
void
Subdiv2D
::
getEdgeList
(
vector
<
Vec4f
>&
edgeList
)
const
{
edgeList
.
clear
();
for
(
size_t
i
=
4
;
i
<
qedges
.
size
();
i
++
)
{
if
(
qedges
[
i
].
isfree
()
)
continue
;
if
(
qedges
[
i
].
pt
[
0
]
>
0
&&
qedges
[
i
].
pt
[
2
]
>
0
)
{
Point2f
org
=
vtx
[
qedges
[
i
].
pt
[
0
]].
pt
;
Point2f
dst
=
vtx
[
qedges
[
i
].
pt
[
2
]].
pt
;
edgeList
.
push_back
(
Vec4f
(
org
.
x
,
org
.
y
,
dst
.
x
,
dst
.
y
));
}
}
}
void
Subdiv2D
::
getTriangleList
(
vector
<
Vec6f
>&
triangleList
)
const
{
triangleList
.
clear
();
int
i
,
total
=
(
int
)(
qedges
.
size
()
*
4
);
vector
<
bool
>
edgemask
(
total
,
false
);
for
(
i
=
4
;
i
<
total
;
i
+=
2
)
{
if
(
edgemask
[
i
]
)
continue
;
Point2f
a
,
b
,
c
;
int
edge
=
i
;
edgeOrg
(
edge
,
&
a
);
edgemask
[
edge
]
=
true
;
edge
=
getEdge
(
edge
,
NEXT_AROUND_LEFT
);
edgeOrg
(
edge
,
&
b
);
edgemask
[
edge
]
=
true
;
edge
=
getEdge
(
edge
,
NEXT_AROUND_LEFT
);
edgeOrg
(
edge
,
&
c
);
edgemask
[
edge
]
=
true
;
triangleList
.
push_back
(
Vec6f
(
a
.
x
,
a
.
y
,
b
.
x
,
b
.
y
,
c
.
x
,
c
.
y
));
}
}
void
Subdiv2D
::
getVoronoiFacetList
(
const
vector
<
int
>&
idx
,
CV_OUT
vector
<
vector
<
Point2f
>
>&
facetList
,
CV_OUT
vector
<
Point2f
>&
facetCenters
)
{
calcVoronoi
();
facetList
.
clear
();
facetCenters
.
clear
();
vector
<
Point2f
>
buf
;
size_t
i
,
total
;
if
(
idx
.
empty
()
)
i
=
4
,
total
=
vtx
.
size
();
else
i
=
0
,
total
=
idx
.
size
();
for
(
;
i
<
total
;
i
++
)
{
int
k
=
idx
.
empty
()
?
(
int
)
i
:
idx
[
i
];
if
(
vtx
[
k
].
isfree
()
||
vtx
[
k
].
isvirtual
()
)
continue
;
int
edge
=
rotateEdge
(
vtx
[
k
].
firstEdge
,
1
),
t
=
edge
;
// gather points
buf
.
clear
();
do
{
buf
.
push_back
(
vtx
[
edgeOrg
(
t
)].
pt
);
t
=
getEdge
(
t
,
NEXT_AROUND_LEFT
);
}
while
(
t
!=
edge
);
facetList
.
push_back
(
buf
);
facetCenters
.
push_back
(
vtx
[
k
].
pt
);
}
}
void
Subdiv2D
::
check
()
const
{
int
i
,
j
,
total
=
(
int
)
qedges
.
size
();
for
(
i
=
0
;
i
<
total
;
i
++
)
{
const
QuadEdge
&
qe
=
qedges
[
i
];
if
(
qe
.
isfree
()
)
continue
;
for
(
j
=
0
;
j
<
4
;
j
++
)
{
int
e
=
(
int
)(
i
*
4
+
j
);
int
o_next
=
nextEdge
(
e
);
int
o_prev
=
getEdge
(
e
,
PREV_AROUND_ORG
);
int
d_prev
=
getEdge
(
e
,
PREV_AROUND_DST
);
int
d_next
=
getEdge
(
e
,
NEXT_AROUND_DST
);
// check points
CV_Assert
(
edgeOrg
(
e
)
==
edgeOrg
(
o_next
));
CV_Assert
(
edgeOrg
(
e
)
==
edgeOrg
(
o_prev
));
CV_Assert
(
edgeDst
(
e
)
==
edgeDst
(
d_next
));
CV_Assert
(
edgeDst
(
e
)
==
edgeDst
(
d_prev
));
if
(
j
%
2
==
0
)
{
CV_Assert
(
edgeDst
(
o_next
)
==
edgeOrg
(
d_prev
));
CV_Assert
(
edgeDst
(
o_prev
)
==
edgeOrg
(
d_next
));
CV_Assert
(
getEdge
(
getEdge
(
getEdge
(
e
,
NEXT_AROUND_LEFT
),
NEXT_AROUND_LEFT
),
NEXT_AROUND_LEFT
)
==
e
);
CV_Assert
(
getEdge
(
getEdge
(
getEdge
(
e
,
NEXT_AROUND_RIGHT
),
NEXT_AROUND_RIGHT
),
NEXT_AROUND_RIGHT
)
==
e
);
}
}
}
}
}
/* End of file. */
modules/python/src2/cv2.cpp
View file @
c9ed7fee
...
...
@@ -58,6 +58,8 @@ typedef vector<Point> vector_Point;
typedef
vector
<
Point2f
>
vector_Point2f
;
typedef
vector
<
Vec2f
>
vector_Vec2f
;
typedef
vector
<
Vec3f
>
vector_Vec3f
;
typedef
vector
<
Vec4f
>
vector_Vec4f
;
typedef
vector
<
Vec6f
>
vector_Vec6f
;
typedef
vector
<
Vec4i
>
vector_Vec4i
;
typedef
vector
<
Rect
>
vector_Rect
;
typedef
vector
<
KeyPoint
>
vector_KeyPoint
;
...
...
modules/python/src2/gen2.py
View file @
c9ed7fee
...
...
@@ -566,7 +566,7 @@ class FuncInfo(object):
amapping
=
simple_argtype_mapping
.
get
(
tp
,
(
tp
,
"O"
,
"0"
))
all_cargs
.
append
(
amapping
)
if
v
.
args
:
if
v
.
args
and
v
.
py_arglist
:
# form the format spec for PyArg_ParseTupleAndKeywords
fmtspec
=
""
.
join
([
all_cargs
[
argno
][
0
][
1
]
for
aname
,
argno
in
v
.
py_arglist
])
if
v
.
py_noptargs
>
0
:
...
...
samples/cpp/delaunay2.cpp
View file @
c9ed7fee
#include <opencv2/opencv.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <iostream>
namespace
cv
{
class
CV_EXPORTS_W
Subdiv2D
{
public
:
enum
{
PTLOC_ERROR
=
-
2
,
PTLOC_OUTSIDE_RECT
=
-
1
,
PTLOC_INSIDE
=
0
,
PTLOC_VERTEX
=
1
,
PTLOC_ON_EDGE
=
2
};
enum
{
NEXT_AROUND_ORG
=
0x00
,
NEXT_AROUND_DST
=
0x22
,
PREV_AROUND_ORG
=
0x11
,
PREV_AROUND_DST
=
0x33
,
NEXT_AROUND_LEFT
=
0x13
,
NEXT_AROUND_RIGHT
=
0x31
,
PREV_AROUND_LEFT
=
0x20
,
PREV_AROUND_RIGHT
=
0x02
};
CV_WRAP
Subdiv2D
();
CV_WRAP
Subdiv2D
(
Rect
rect
);
CV_WRAP
void
initDelaunay
(
Rect
rect
);
CV_WRAP
int
insert
(
Point2f
pt
);
CV_WRAP
void
insert
(
const
vector
<
Point2f
>&
ptvec
);
CV_WRAP
int
locate
(
Point2f
pt
,
CV_OUT
int
&
edge
,
CV_OUT
int
&
vertex
);
CV_WRAP
int
findNearest
(
Point2f
pt
,
CV_OUT
Point2f
*
nearestPt
=
0
);
CV_WRAP
void
getTriangleList
(
CV_OUT
vector
<
Vec6f
>&
triangleList
);
CV_WRAP
void
getVoronoiFacetList
(
const
vector
<
int
>&
idx
,
CV_OUT
vector
<
vector
<
Point2f
>
>&
facetList
);
CV_WRAP
Point2f
getVertex
(
int
vertex
,
CV_OUT
int
*
firstEdge
=
0
)
const
;
CV_WRAP
int
getEdge
(
int
edge
,
int
nextEdgeType
)
const
;
CV_WRAP
int
nextEdge
(
int
edge
)
const
;
CV_WRAP
int
rotateEdge
(
int
edge
,
int
rotate
)
const
;
CV_WRAP
int
symEdge
(
int
edge
)
const
;
CV_WRAP
int
edgeOrg
(
int
edge
,
CV_OUT
Point2f
*
orgpt
=
0
)
const
;
CV_WRAP
int
edgeDst
(
int
edge
,
CV_OUT
Point2f
*
dstpt
=
0
)
const
;
protected
:
int
newEdge
();
void
deleteEdge
(
int
edge
);
int
newPoint
(
Point2f
pt
,
bool
isvirtual
,
int
firstEdge
=
0
);
void
deletePoint
(
int
vtx
);
void
setEdgePoints
(
int
edge
,
int
orgPt
,
int
dstPt
);
void
splice
(
int
edgeA
,
int
edgeB
);
int
connectEdges
(
int
edgeA
,
int
edgeB
);
void
swapEdges
(
int
edge
);
int
isRightOf
(
Point2f
pt
,
int
edge
)
const
;
void
calcVoronoi
();
void
clearVoronoi
();
struct
CV_EXPORTS
Vertex
{
Vertex
();
Vertex
(
Point2f
pt
,
bool
_isvirtual
,
int
_firstEdge
=
0
);
bool
isvirtual
()
const
;
bool
isfree
()
const
;
int
firstEdge
;
int
type
;
Point2f
pt
;
};
struct
CV_EXPORTS
QuadEdge
{
QuadEdge
();
QuadEdge
(
int
edgeidx
);
bool
isfree
()
const
;
int
next
[
4
];
int
pt
[
4
];
};
vector
<
Vertex
>
vtx
;
vector
<
QuadEdge
>
qedges
;
int
freeQEdge
;
int
freePoint
;
bool
validGeometry
;
int
recentEdge
;
Point2f
topLeft
;
Point2f
bottomRight
;
};
int
Subdiv2D
::
nextEdge
(
int
edge
)
const
{
CV_DbgAssert
((
size_t
)(
edge
>>
2
)
<
qedges
.
size
());
return
qedges
[
edge
>>
2
].
next
[
edge
&
3
];
}
int
Subdiv2D
::
rotateEdge
(
int
edge
,
int
rotate
)
const
{
CV_DbgAssert
((
size_t
)(
edge
>>
2
)
<
qedges
.
size
());
return
(
edge
&
~
3
)
+
((
edge
+
rotate
)
&
3
);
}
int
Subdiv2D
::
symEdge
(
int
edge
)
const
{
CV_DbgAssert
((
size_t
)(
edge
>>
2
)
<
qedges
.
size
());
return
edge
^
2
;
}
int
Subdiv2D
::
getEdge
(
int
edge
,
int
nextEdgeType
)
const
{
CV_DbgAssert
((
size_t
)(
edge
>>
2
)
<
qedges
.
size
());
edge
=
qedges
[
edge
>>
2
].
next
[(
edge
+
nextEdgeType
)
&
3
];
return
(
edge
&
~
3
)
+
((
edge
+
(
nextEdgeType
>>
4
))
&
3
);
}
int
Subdiv2D
::
edgeOrg
(
int
edge
,
CV_OUT
Point2f
*
orgpt
)
const
{
CV_DbgAssert
((
size_t
)(
edge
>>
2
)
<
qedges
.
size
());
int
vidx
=
qedges
[
edge
>>
2
].
pt
[
edge
&
3
];
if
(
orgpt
)
{
CV_DbgAssert
((
size_t
)
vidx
<
vtx
.
size
());
*
orgpt
=
vtx
[
vidx
].
pt
;
}
return
vidx
;
}
int
Subdiv2D
::
edgeDst
(
int
edge
,
CV_OUT
Point2f
*
dstpt
)
const
{
CV_DbgAssert
((
size_t
)(
edge
>>
2
)
<
qedges
.
size
());
int
vidx
=
qedges
[
edge
>>
2
].
pt
[(
edge
+
2
)
&
3
];
if
(
dstpt
)
{
CV_DbgAssert
((
size_t
)
vidx
<
vtx
.
size
());
*
dstpt
=
vtx
[
vidx
].
pt
;
}
return
vidx
;
}
Point2f
Subdiv2D
::
getVertex
(
int
vertex
,
CV_OUT
int
*
firstEdge
)
const
{
CV_DbgAssert
((
size_t
)
vertex
<
vtx
.
size
());
if
(
firstEdge
)
*
firstEdge
=
vtx
[
vertex
].
firstEdge
;
return
vtx
[
vertex
].
pt
;
}
Subdiv2D
::
Subdiv2D
()
{
validGeometry
=
false
;
freeQEdge
=
0
;
freePoint
=
0
;
recentEdge
=
0
;
}
Subdiv2D
::
Subdiv2D
(
Rect
rect
)
{
validGeometry
=
false
;
freeQEdge
=
0
;
freePoint
=
0
;
recentEdge
=
0
;
initDelaunay
(
rect
);
}
Subdiv2D
::
QuadEdge
::
QuadEdge
()
{
next
[
0
]
=
next
[
1
]
=
next
[
2
]
=
next
[
3
]
=
0
;
pt
[
0
]
=
pt
[
1
]
=
pt
[
2
]
=
pt
[
3
]
=
0
;
}
Subdiv2D
::
QuadEdge
::
QuadEdge
(
int
edgeidx
)
{
next
[
0
]
=
edgeidx
;
next
[
1
]
=
edgeidx
+
3
;
next
[
2
]
=
edgeidx
+
2
;
next
[
3
]
=
edgeidx
+
1
;
pt
[
0
]
=
pt
[
1
]
=
pt
[
2
]
=
pt
[
3
]
=
0
;
}
bool
Subdiv2D
::
QuadEdge
::
isfree
()
const
{
return
next
[
0
]
<=
0
;
}
Subdiv2D
::
Vertex
::
Vertex
()
{
firstEdge
=
0
;
type
=
-
1
;
}
Subdiv2D
::
Vertex
::
Vertex
(
Point2f
_pt
,
bool
_isvirtual
,
int
_firstEdge
)
{
firstEdge
=
_firstEdge
;
type
=
(
int
)
_isvirtual
;
pt
=
_pt
;
}
bool
Subdiv2D
::
Vertex
::
isvirtual
()
const
{
return
type
>
0
;
}
bool
Subdiv2D
::
Vertex
::
isfree
()
const
{
return
firstEdge
<=
0
;
}
void
Subdiv2D
::
splice
(
int
edgeA
,
int
edgeB
)
{
int
&
a_next
=
qedges
[
edgeA
>>
2
].
next
[
edgeA
&
3
];
int
&
b_next
=
qedges
[
edgeB
>>
2
].
next
[
edgeB
&
3
];
int
a_rot
=
rotateEdge
(
a_next
,
1
);
int
b_rot
=
rotateEdge
(
b_next
,
1
);
int
&
a_rot_next
=
qedges
[
a_rot
>>
2
].
next
[
a_rot
&
3
];
int
&
b_rot_next
=
qedges
[
b_rot
>>
2
].
next
[
b_rot
&
3
];
std
::
swap
(
a_next
,
b_next
);
std
::
swap
(
a_rot_next
,
b_rot_next
);
}
void
Subdiv2D
::
setEdgePoints
(
int
edge
,
int
orgPt
,
int
dstPt
)
{
qedges
[
edge
>>
2
].
pt
[
edge
&
3
]
=
orgPt
;
qedges
[
edge
>>
2
].
pt
[(
edge
+
2
)
&
3
]
=
dstPt
;
}
int
Subdiv2D
::
connectEdges
(
int
edgeA
,
int
edgeB
)
{
int
edge
=
newEdge
();
splice
(
edge
,
getEdge
(
edgeA
,
NEXT_AROUND_LEFT
));
splice
(
symEdge
(
edge
),
edgeB
);
setEdgePoints
(
edge
,
edgeDst
(
edgeA
),
edgeOrg
(
edgeB
));
return
edge
;
}
void
Subdiv2D
::
swapEdges
(
int
edge
)
{
int
sedge
=
symEdge
(
edge
);
int
a
=
getEdge
(
edge
,
PREV_AROUND_ORG
);
int
b
=
getEdge
(
sedge
,
PREV_AROUND_ORG
);
splice
(
edge
,
a
);
splice
(
sedge
,
b
);
setEdgePoints
(
edge
,
edgeDst
(
a
),
edgeDst
(
b
));
splice
(
edge
,
getEdge
(
a
,
NEXT_AROUND_LEFT
));
splice
(
sedge
,
getEdge
(
b
,
NEXT_AROUND_LEFT
));
}
int
Subdiv2D
::
isRightOf
(
Point2f
pt
,
int
edge
)
const
{
Point2f
org
,
dst
;
edgeOrg
(
edge
,
&
org
);
edgeDst
(
edge
,
&
dst
);
double
cw_area
=
cvTriangleArea
(
pt
,
dst
,
org
);
return
(
cw_area
>
0
)
-
(
cw_area
<
0
);
}
int
Subdiv2D
::
newEdge
()
{
if
(
freeQEdge
==
0
)
{
qedges
.
push_back
(
QuadEdge
());
freeQEdge
=
(
int
)(
qedges
.
size
()
-
1
);
}
int
edge
=
freeQEdge
*
4
;
freeQEdge
=
qedges
[
edge
>>
2
].
next
[
1
];
qedges
[
edge
>>
2
]
=
QuadEdge
(
edge
);
return
edge
;
}
void
Subdiv2D
::
deleteEdge
(
int
edge
)
{
CV_DbgAssert
((
size_t
)(
edge
>>
2
)
<
(
size_t
)
qedges
.
size
());
splice
(
edge
,
getEdge
(
edge
,
PREV_AROUND_ORG
)
);
int
sedge
=
symEdge
(
edge
);
splice
(
sedge
,
getEdge
(
sedge
,
PREV_AROUND_ORG
)
);
edge
>>=
2
;
qedges
[
edge
].
next
[
0
]
=
-
1
;
qedges
[
edge
].
next
[
1
]
=
freeQEdge
;
freeQEdge
=
edge
;
}
int
Subdiv2D
::
newPoint
(
Point2f
pt
,
bool
isvirtual
,
int
firstEdge
)
{
if
(
freePoint
==
0
)
{
vtx
.
push_back
(
Vertex
());
freePoint
=
(
int
)(
vtx
.
size
()
-
1
);
vtx
[
freePoint
].
type
=
-
1
;
vtx
[
freePoint
].
firstEdge
=
0
;
}
int
vidx
=
freePoint
;
freePoint
=
vtx
[
vidx
].
firstEdge
;
vtx
[
vidx
]
=
Vertex
(
pt
,
isvirtual
,
firstEdge
);
return
vidx
;
}
void
Subdiv2D
::
deletePoint
(
int
vidx
)
{
CV_DbgAssert
(
(
size_t
)
vidx
<
vtx
.
size
()
);
vtx
[
vidx
].
firstEdge
=
freePoint
;
vtx
[
vidx
].
type
=
-
1
;
freePoint
=
vidx
;
}
int
Subdiv2D
::
locate
(
Point2f
pt
,
int
&
_edge
,
int
&
_vertex
)
{
int
vertex
=
0
;
int
i
,
maxEdges
=
(
int
)(
qedges
.
size
()
*
4
);
int
edge
=
recentEdge
;
CV_Assert
(
edge
>
0
);
if
(
pt
.
x
<
topLeft
.
x
||
pt
.
y
<
topLeft
.
y
||
pt
.
x
>=
bottomRight
.
x
||
pt
.
y
>=
bottomRight
.
y
)
CV_Error
(
CV_StsOutOfRange
,
""
);
int
location
=
PTLOC_ERROR
;
int
right_of_curr
=
isRightOf
(
pt
,
edge
);
if
(
right_of_curr
>
0
)
{
edge
=
symEdge
(
edge
);
right_of_curr
=
-
right_of_curr
;
}
for
(
i
=
0
;
i
<
maxEdges
;
i
++
)
{
int
onext_edge
=
nextEdge
(
edge
);
int
dprev_edge
=
getEdge
(
edge
,
PREV_AROUND_DST
);
int
right_of_onext
=
isRightOf
(
pt
,
onext_edge
);
int
right_of_dprev
=
isRightOf
(
pt
,
dprev_edge
);
if
(
right_of_dprev
>
0
)
{
if
(
right_of_onext
>
0
||
(
right_of_onext
==
0
&&
right_of_curr
==
0
)
)
{
location
=
PTLOC_INSIDE
;
break
;
}
else
{
right_of_curr
=
right_of_onext
;
edge
=
onext_edge
;
}
}
else
{
if
(
right_of_onext
>
0
)
{
if
(
right_of_dprev
==
0
&&
right_of_curr
==
0
)
{
location
=
PTLOC_INSIDE
;
break
;
}
else
{
right_of_curr
=
right_of_dprev
;
edge
=
dprev_edge
;
}
}
else
if
(
right_of_curr
==
0
&&
isRightOf
(
vtx
[
edgeDst
(
onext_edge
)].
pt
,
edge
)
>=
0
)
{
edge
=
symEdge
(
edge
);
}
else
{
right_of_curr
=
right_of_onext
;
edge
=
onext_edge
;
}
}
}
recentEdge
=
edge
;
if
(
location
==
PTLOC_INSIDE
)
{
Point2f
org_pt
,
dst_pt
;
edgeOrg
(
edge
,
&
org_pt
);
edgeDst
(
edge
,
&
dst_pt
);
double
t1
=
fabs
(
pt
.
x
-
org_pt
.
x
);
t1
+=
fabs
(
pt
.
y
-
org_pt
.
y
);
double
t2
=
fabs
(
pt
.
x
-
dst_pt
.
x
);
t2
+=
fabs
(
pt
.
y
-
dst_pt
.
y
);
double
t3
=
fabs
(
org_pt
.
x
-
dst_pt
.
x
);
t3
+=
fabs
(
org_pt
.
y
-
dst_pt
.
y
);
if
(
t1
<
FLT_EPSILON
)
{
location
=
PTLOC_VERTEX
;
vertex
=
edgeOrg
(
edge
);
edge
=
0
;
}
else
if
(
t2
<
FLT_EPSILON
)
{
location
=
PTLOC_VERTEX
;
vertex
=
edgeDst
(
edge
);
edge
=
0
;
}
else
if
(
(
t1
<
t3
||
t2
<
t3
)
&&
fabs
(
cvTriangleArea
(
pt
,
org_pt
,
dst_pt
))
<
FLT_EPSILON
)
{
location
=
PTLOC_ON_EDGE
;
vertex
=
0
;
}
}
if
(
location
==
PTLOC_ERROR
)
{
edge
=
0
;
vertex
=
0
;
}
_edge
=
edge
;
_vertex
=
vertex
;
return
location
;
}
inline
bool
isPtInCircle3
(
Point2f
pt
,
Point2f
a
,
Point2f
b
,
Point2f
c
)
{
const
double
eps
=
FLT_EPSILON
*
0.125
;
double
val
=
((
double
)
a
.
x
*
a
.
x
+
(
double
)
a
.
y
*
a
.
y
)
*
cvTriangleArea
(
b
,
c
,
pt
);
val
-=
((
double
)
b
.
x
*
b
.
x
+
(
double
)
b
.
y
*
b
.
y
)
*
cvTriangleArea
(
a
,
c
,
pt
);
val
+=
((
double
)
c
.
x
*
c
.
x
+
(
double
)
c
.
y
*
c
.
y
)
*
cvTriangleArea
(
a
,
b
,
pt
);
val
-=
((
double
)
pt
.
x
*
pt
.
x
+
(
double
)
pt
.
y
*
pt
.
y
)
*
cvTriangleArea
(
a
,
b
,
c
);
return
val
>
eps
?
1
:
val
<
-
eps
?
-
1
:
0
;
}
int
Subdiv2D
::
insert
(
Point2f
pt
)
{
int
curr_point
=
0
,
curr_edge
=
0
,
deleted_edge
=
0
;
int
location
=
locate
(
pt
,
curr_edge
,
curr_point
);
if
(
location
==
PTLOC_ERROR
)
CV_Error
(
CV_StsBadSize
,
""
);
if
(
location
==
PTLOC_OUTSIDE_RECT
)
CV_Error
(
CV_StsOutOfRange
,
""
);
if
(
location
==
PTLOC_VERTEX
)
return
curr_point
;
if
(
location
==
PTLOC_ON_EDGE
)
{
deleted_edge
=
curr_edge
;
recentEdge
=
curr_edge
=
getEdge
(
curr_edge
,
PREV_AROUND_ORG
);
deleteEdge
(
deleted_edge
);
}
else
if
(
location
==
PTLOC_INSIDE
)
;
else
CV_Error_
(
CV_StsError
,
(
"Subdiv2D::locate returned invalid location = %d"
,
location
)
);
assert
(
curr_edge
!=
0
);
validGeometry
=
false
;
curr_point
=
newPoint
(
pt
,
false
);
int
base_edge
=
newEdge
();
int
first_point
=
edgeOrg
(
curr_edge
);
setEdgePoints
(
base_edge
,
first_point
,
curr_point
);
splice
(
base_edge
,
curr_edge
);
do
{
base_edge
=
connectEdges
(
curr_edge
,
symEdge
(
base_edge
)
);
curr_edge
=
getEdge
(
base_edge
,
PREV_AROUND_ORG
);
}
while
(
edgeDst
(
curr_edge
)
!=
first_point
);
curr_edge
=
getEdge
(
base_edge
,
PREV_AROUND_ORG
);
int
i
,
max_edges
=
qedges
.
size
()
*
4
;
for
(
i
=
0
;
i
<
max_edges
;
i
++
)
{
int
temp_dst
=
0
,
curr_org
=
0
,
curr_dst
=
0
;
int
temp_edge
=
getEdge
(
curr_edge
,
PREV_AROUND_ORG
);
temp_dst
=
edgeDst
(
temp_edge
);
curr_org
=
edgeOrg
(
curr_edge
);
curr_dst
=
edgeDst
(
curr_edge
);
if
(
isRightOf
(
vtx
[
temp_dst
].
pt
,
curr_edge
)
>
0
&&
isPtInCircle3
(
vtx
[
curr_org
].
pt
,
vtx
[
temp_dst
].
pt
,
vtx
[
curr_dst
].
pt
,
vtx
[
curr_point
].
pt
)
<
0
)
{
swapEdges
(
curr_edge
);
curr_edge
=
getEdge
(
curr_edge
,
PREV_AROUND_ORG
);
}
else
if
(
curr_org
==
first_point
)
break
;
else
curr_edge
=
getEdge
(
nextEdge
(
curr_edge
),
PREV_AROUND_LEFT
);
}
return
curr_point
;
}
void
Subdiv2D
::
insert
(
const
vector
<
Point2f
>&
ptvec
)
{
for
(
size_t
i
=
0
;
i
<
ptvec
.
size
();
i
++
)
insert
(
ptvec
[
i
]);
}
void
Subdiv2D
::
initDelaunay
(
Rect
rect
)
{
float
big_coord
=
3.
f
*
MAX
(
rect
.
width
,
rect
.
height
);
float
rx
=
(
float
)
rect
.
x
;
float
ry
=
(
float
)
rect
.
y
;
vtx
.
clear
();
qedges
.
clear
();
recentEdge
=
0
;
validGeometry
=
false
;
topLeft
=
Point2f
(
rx
,
ry
);
bottomRight
=
Point2f
(
rx
+
rect
.
width
,
ry
+
rect
.
height
);
Point2f
ppA
(
rx
+
big_coord
,
ry
);
Point2f
ppB
(
rx
,
ry
+
big_coord
);
Point2f
ppC
(
rx
-
big_coord
,
ry
-
big_coord
);
vtx
.
push_back
(
Vertex
());
qedges
.
push_back
(
QuadEdge
());
freeQEdge
=
0
;
freePoint
=
0
;
int
pA
=
newPoint
(
ppA
,
false
);
int
pB
=
newPoint
(
ppB
,
false
);
int
pC
=
newPoint
(
ppC
,
false
);
int
edge_AB
=
newEdge
();
int
edge_BC
=
newEdge
();
int
edge_CA
=
newEdge
();
setEdgePoints
(
edge_AB
,
pA
,
pB
);
setEdgePoints
(
edge_BC
,
pB
,
pC
);
setEdgePoints
(
edge_CA
,
pC
,
pA
);
splice
(
edge_AB
,
symEdge
(
edge_CA
));
splice
(
edge_BC
,
symEdge
(
edge_AB
));
splice
(
edge_CA
,
symEdge
(
edge_BC
));
recentEdge
=
edge_AB
;
}
void
Subdiv2D
::
clearVoronoi
()
{
size_t
i
,
total
=
qedges
.
size
();
for
(
i
=
0
;
i
<
total
;
i
++
)
qedges
[
i
].
pt
[
1
]
=
qedges
[
i
].
pt
[
3
]
=
0
;
total
=
vtx
.
size
();
for
(
i
=
0
;
i
<
total
;
i
++
)
{
if
(
vtx
[
i
].
isvirtual
()
)
deletePoint
((
int
)
i
);
}
validGeometry
=
false
;
}
static
Point2f
computeVoronoiPoint
(
Point2f
org0
,
Point2f
dst0
,
Point2f
org1
,
Point2f
dst1
)
{
double
a0
=
dst0
.
x
-
org0
.
x
;
double
b0
=
dst0
.
y
-
org0
.
y
;
double
c0
=
-
0.5
*
(
a0
*
(
dst0
.
x
+
org0
.
x
)
+
b0
*
(
dst0
.
y
+
org0
.
y
));
double
a1
=
dst1
.
x
-
org1
.
x
;
double
b1
=
dst1
.
y
-
org1
.
y
;
double
c1
=
-
0.5
*
(
a1
*
(
dst1
.
x
+
org1
.
x
)
+
b1
*
(
dst1
.
y
+
org1
.
y
));
double
det
=
a0
*
b1
-
a1
*
b0
;
if
(
det
!=
0
)
{
det
=
1.
/
det
;
return
Point2f
((
float
)
((
b0
*
c1
-
b1
*
c0
)
*
det
),
(
float
)
((
a1
*
c0
-
a0
*
c1
)
*
det
));
}
return
Point2f
(
FLT_MAX
,
FLT_MAX
);
}
void
Subdiv2D
::
calcVoronoi
()
{
// check if it is already calculated
if
(
validGeometry
)
return
;
clearVoronoi
();
int
i
,
total
=
(
int
)
qedges
.
size
();
// loop through all quad-edges, except for the first 3 (#1, #2, #3 - 0 is reserved for "NULL" pointer)
for
(
i
=
4
;
i
<
total
;
i
++
)
{
QuadEdge
&
quadedge
=
qedges
[
i
];
if
(
quadedge
.
isfree
()
)
continue
;
int
edge0
=
(
int
)(
i
*
4
);
Point2f
org0
,
dst0
,
org1
,
dst1
;
if
(
!
quadedge
.
pt
[
3
]
)
{
int
edge1
=
getEdge
(
edge0
,
NEXT_AROUND_LEFT
);
int
edge2
=
getEdge
(
edge1
,
NEXT_AROUND_LEFT
);
edgeOrg
(
edge0
,
&
org0
);
edgeDst
(
edge0
,
&
dst0
);
edgeOrg
(
edge1
,
&
org1
);
edgeDst
(
edge1
,
&
dst1
);
Point2f
virt_point
=
computeVoronoiPoint
(
org0
,
dst0
,
org1
,
dst1
);
if
(
fabs
(
virt_point
.
x
)
<
FLT_MAX
*
0.5
&&
fabs
(
virt_point
.
y
)
<
FLT_MAX
*
0.5
)
{
quadedge
.
pt
[
3
]
=
qedges
[
edge1
>>
2
].
pt
[
3
-
(
edge1
&
2
)]
=
qedges
[
edge2
>>
2
].
pt
[
3
-
(
edge2
&
2
)]
=
newPoint
(
virt_point
,
true
);
}
}
if
(
!
quadedge
.
pt
[
1
]
)
{
int
edge1
=
getEdge
(
edge0
,
NEXT_AROUND_RIGHT
);
int
edge2
=
getEdge
(
edge1
,
NEXT_AROUND_RIGHT
);
edgeOrg
(
edge0
,
&
org0
);
edgeDst
(
edge0
,
&
dst0
);
edgeOrg
(
edge1
,
&
org1
);
edgeDst
(
edge1
,
&
dst1
);
Point2f
virt_point
=
computeVoronoiPoint
(
org0
,
dst0
,
org1
,
dst1
);
if
(
fabs
(
virt_point
.
x
)
<
FLT_MAX
*
0.5
&&
fabs
(
virt_point
.
y
)
<
FLT_MAX
*
0.5
)
{
quadedge
.
pt
[
1
]
=
qedges
[
edge1
>>
2
].
pt
[
1
+
(
edge1
&
2
)]
=
qedges
[
edge2
>>
2
].
pt
[
1
+
(
edge2
&
2
)]
=
newPoint
(
virt_point
,
true
);
}
}
}
validGeometry
=
true
;
}
static
int
isRightOf2
(
const
Point2f
&
pt
,
const
Point2f
&
org
,
const
Point2f
&
diff
)
{
double
cw_area
=
((
double
)
org
.
x
-
pt
.
x
)
*
diff
.
y
-
((
double
)
org
.
y
-
pt
.
y
)
*
diff
.
x
;
return
(
cw_area
>
0
)
-
(
cw_area
<
0
);
}
int
Subdiv2D
::
findNearest
(
Point2f
pt
,
Point2f
*
nearestPt
)
{
if
(
!
validGeometry
)
calcVoronoi
();
int
vertex
=
0
,
edge
=
0
;
int
loc
=
locate
(
pt
,
edge
,
vertex
);
if
(
loc
!=
PTLOC_ON_EDGE
&&
loc
!=
PTLOC_INSIDE
)
return
vertex
;
vertex
=
0
;
Point2f
start
;
edgeOrg
(
edge
,
&
start
);
Point2f
diff
=
pt
-
start
;
edge
=
rotateEdge
(
edge
,
1
);
int
i
,
total
=
(
int
)
vtx
.
size
();
for
(
i
=
0
;
i
<
total
;
i
++
)
{
Point2f
t
;
for
(;;)
{
CV_Assert
(
edgeDst
(
edge
,
&
t
)
>
0
);
if
(
isRightOf2
(
t
,
start
,
diff
)
>=
0
)
break
;
edge
=
getEdge
(
edge
,
NEXT_AROUND_LEFT
);
}
for
(;;)
{
CV_Assert
(
edgeOrg
(
edge
,
&
t
)
>
0
);
if
(
isRightOf2
(
t
,
start
,
diff
)
<
0
)
break
;
edge
=
getEdge
(
edge
,
PREV_AROUND_LEFT
);
}
Point2f
tempDiff
;
edgeDst
(
edge
,
&
tempDiff
);
edgeOrg
(
edge
,
&
t
);
tempDiff
-=
t
;
if
(
isRightOf2
(
pt
,
t
,
tempDiff
)
>=
0
)
{
vertex
=
edgeOrg
(
rotateEdge
(
edge
,
3
));
break
;
}
edge
=
symEdge
(
edge
);
}
if
(
nearestPt
&&
vertex
>
0
)
*
nearestPt
=
vtx
[
vertex
].
pt
;
return
vertex
;
}
void
Subdiv2D
::
getTriangleList
(
vector
<
Vec6f
>&
triangleList
)
{
vector
<
bool
>
processed
(
vtx
.
size
(),
false
);
processed
[
0
]
=
true
;
calcVoronoi
();
triangleList
.
clear
();
for
(
size_t
i
=
4
;
i
<
qedges
.
size
();
i
++
)
{
if
(
qedges
[
i
].
isfree
()
)
continue
;
int
e0
=
(
int
)(
i
*
4
),
e1
=
rotateEdge
(
e0
,
1
),
e
;
int
vidx0
=
edgeOrg
(
e1
),
vidx1
=
edgeDst
(
e1
);
Point2f
a
,
b
,
c
;
if
(
!
processed
[
vidx0
]
)
{
edgeOrg
(
e0
,
&
a
);
edgeDst
(
e0
,
&
b
);
e
=
getEdge
(
e0
,
NEXT_AROUND_LEFT
);
edgeDst
(
e
,
&
c
);
triangleList
.
push_back
(
Vec6f
(
a
.
x
,
a
.
y
,
b
.
x
,
b
.
y
,
c
.
x
,
c
.
y
));
processed
[
vidx0
]
=
true
;
}
if
(
!
processed
[
vidx1
]
)
{
edgeDst
(
e0
,
&
a
);
edgeOrg
(
e0
,
&
b
);
e
=
getEdge
(
e0
,
PREV_AROUND_RIGHT
);
edgeOrg
(
e
,
&
c
);
triangleList
.
push_back
(
Vec6f
(
a
.
x
,
a
.
y
,
b
.
x
,
b
.
y
,
c
.
x
,
c
.
y
));
processed
[
vidx1
]
=
true
;
}
}
}
void
Subdiv2D
::
getVoronoiFacetList
(
const
vector
<
int
>&
idx
,
CV_OUT
vector
<
vector
<
Point2f
>
>&
facetList
)
{
calcVoronoi
();
facetList
.
clear
();
vector
<
Point2f
>
buf
;
size_t
i
,
total
;
if
(
idx
.
empty
()
)
i
=
4
,
total
=
vtx
.
size
();
else
i
=
0
,
total
=
idx
.
size
();
for
(
;
i
<
total
;
i
++
)
{
int
k
=
idx
.
empty
()
?
(
int
)
i
:
idx
[
i
];
if
(
vtx
[
k
].
isvirtual
()
)
continue
;
int
edge
=
rotateEdge
(
vtx
[
k
].
firstEdge
,
1
),
t
=
edge
;
// gather points
buf
.
clear
();
do
{
buf
.
push_back
(
vtx
[
edgeOrg
(
t
)].
pt
);
t
=
getEdge
(
t
,
NEXT_AROUND_LEFT
);
}
while
(
t
!=
edge
);
facetList
.
push_back
(
buf
);
}
}
}
using
namespace
cv
;
using
namespace
std
;
...
...
@@ -837,7 +16,6 @@ static void help()
"hitting any key.
\n
"
;
}
static
void
draw_subdiv_point
(
Mat
&
img
,
Point2f
fp
,
Scalar
color
)
{
circle
(
img
,
fp
,
3
,
color
,
CV_FILLED
,
8
,
0
);
...
...
@@ -845,6 +23,7 @@ static void draw_subdiv_point( Mat& img, Point2f fp, Scalar color )
static
void
draw_subdiv
(
Mat
&
img
,
Subdiv2D
&
subdiv
,
Scalar
delaunay_color
)
{
#if 1
vector
<
Vec6f
>
triangleList
;
subdiv
.
getTriangleList
(
triangleList
);
vector
<
Point
>
pt
(
3
);
...
...
@@ -859,12 +38,25 @@ static void draw_subdiv( Mat& img, Subdiv2D& subdiv, Scalar delaunay_color )
line
(
img
,
pt
[
1
],
pt
[
2
],
delaunay_color
,
1
,
CV_AA
,
0
);
line
(
img
,
pt
[
2
],
pt
[
0
],
delaunay_color
,
1
,
CV_AA
,
0
);
}
#else
vector
<
Vec4f
>
edgeList
;
subdiv
.
getEdgeList
(
edgeList
);
for
(
size_t
i
=
0
;
i
<
edgeList
.
size
();
i
++
)
{
Vec4f
e
=
edgeList
[
i
];
Point
pt0
=
Point
(
cvRound
(
e
[
0
]),
cvRound
(
e
[
1
]));
Point
pt1
=
Point
(
cvRound
(
e
[
2
]),
cvRound
(
e
[
3
]));
line
(
img
,
pt0
,
pt1
,
delaunay_color
,
1
,
CV_AA
,
0
);
}
#endif
}
static
void
locate_point
(
Mat
&
img
,
Subdiv2D
&
subdiv
,
Point2f
fp
,
Scalar
active_color
)
{
int
e0
=
0
,
vertex
=
0
;
CvSubdiv2DEdge
e00
;
subdiv
.
locate
(
fp
,
e0
,
vertex
);
if
(
e0
>
0
)
...
...
@@ -888,7 +80,8 @@ static void locate_point( Mat& img, Subdiv2D& subdiv, Point2f fp, Scalar active_
void
paint_voronoi
(
Mat
&
img
,
Subdiv2D
&
subdiv
)
{
vector
<
vector
<
Point2f
>
>
facets
;
subdiv
.
getVoronoiFacetList
(
vector
<
int
>
(),
facets
);
vector
<
Point2f
>
centers
;
subdiv
.
getVoronoiFacetList
(
vector
<
int
>
(),
facets
,
centers
);
vector
<
Point
>
ifacet
;
vector
<
vector
<
Point
>
>
ifacets
(
1
);
...
...
@@ -900,13 +93,14 @@ void paint_voronoi( Mat& img, Subdiv2D& subdiv )
ifacet
[
j
]
=
facets
[
i
][
j
];
Scalar
color
;
color
[
0
]
=
rand
()
&
25
6
;
color
[
1
]
=
rand
()
&
25
6
;
color
[
2
]
=
rand
()
&
25
6
;
color
[
0
]
=
rand
()
&
25
5
;
color
[
1
]
=
rand
()
&
25
5
;
color
[
2
]
=
rand
()
&
25
5
;
fillConvexPoly
(
img
,
ifacet
,
color
,
8
,
0
);
ifacets
[
0
]
=
ifacet
;
polylines
(
img
,
ifacets
,
true
,
Scalar
(),
1
,
CV_AA
,
0
);
circle
(
img
,
centers
[
i
],
3
,
Scalar
(),
-
1
,
CV_AA
,
0
);
}
}
...
...
@@ -937,6 +131,7 @@ int main( int, char** )
break
;
subdiv
.
insert
(
fp
);
img
=
Scalar
::
all
(
0
);
draw_subdiv
(
img
,
subdiv
,
delaunay_color
);
imshow
(
win
,
img
);
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment