forked from bytedeco/javacv
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Square.java
307 lines (262 loc) · 13.1 KB
/
Square.java
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
import java.awt.event.KeyEvent;
import org.bytedeco.javacpp.*;
import org.bytedeco.javacv.*;
import org.bytedeco.opencv.opencv_core.*;
import org.bytedeco.opencv.opencv_imgproc.*;
import static org.bytedeco.opencv.global.opencv_core.*;
import static org.bytedeco.opencv.global.opencv_imgproc.*;
import static org.bytedeco.opencv.global.opencv_imgcodecs.*;
/**
* I was unable to find the OpenCV squares.c translated into JavaCV, so this
* is a line-by-line translation of the C source.
* The squares.c source used, found here:
* https://code.ros.org/trac/opencv/browser/trunk/opencv/samples/c/squares.c?rev=1429
*
* This is a demo class for finding squares/rectangles in an image, using JavaCV.
*
* All individual imports are kept as is; if you are like me,
* you probably dislike the catch all .* import when trying to understand stuff.
*
* The major headache of the C code was figuring out the
* "drawLines" method, and what parameters "cvPolyLine" is supposed to use.
*
* @author [email protected]
*/
public class Square {
int thresh = 50;
IplImage img = null;
IplImage img0 = null;
CvMemStorage storage = null;
String wndname = "Square Detection Demo";
// Java spesific
CanvasFrame canvas = null;
OpenCVFrameConverter.ToIplImage converter = new OpenCVFrameConverter.ToIplImage();
// helper function:
// finds a cosine of angle between vectors
// from pt0->pt1 and from pt0->pt2
double angle(CvPoint pt1, CvPoint pt2, CvPoint pt0) {
double dx1 = pt1.x() - pt0.x();
double dy1 = pt1.y() - pt0.y();
double dx2 = pt2.x() - pt0.x();
double dy2 = pt2.y() - pt0.y();
return (dx1*dx2 + dy1*dy2) / Math.sqrt((dx1*dx1 + dy1*dy1) * (dx2*dx2 + dy2*dy2) + 1e-10);
}
// returns sequence of squares detected on the image.
// the sequence is stored in the specified memory storage
CvSeq findSquares4(IplImage img, CvMemStorage storage) {
// Java translation: moved into loop
// CvSeq contours = new CvSeq();
int i, c, l, N = 11;
CvSize sz = cvSize(img.width() & -2, img.height() & -2);
IplImage timg = cvCloneImage(img); // make a copy of input image
IplImage gray = cvCreateImage(sz, 8, 1);
IplImage pyr = cvCreateImage(cvSize(sz.width()/2, sz.height()/2), 8, 3);
IplImage tgray = null;
// Java translation: moved into loop
// CvSeq result = null;
// double s = 0.0, t = 0.0;
// create empty sequence that will contain points -
// 4 points per square (the square's vertices)
CvSeq squares = cvCreateSeq(0, Loader.sizeof(CvSeq.class), Loader.sizeof(CvPoint.class), storage);
// select the maximum ROI in the image
// with the width and height divisible by 2
cvSetImageROI(timg, cvRect(0, 0, sz.width(), sz.height()));
// down-scale and upscale the image to filter out the noise
cvPyrDown(timg, pyr, 7);
cvPyrUp(pyr, timg, 7);
tgray = cvCreateImage(sz, 8, 1);
// find squares in every color plane of the image
for (c = 0; c < 3; c++) {
// extract the c-th color plane
cvSetImageCOI(timg, c+1);
cvCopy(timg, tgray);
// try several threshold levels
for (l = 0; l < N; l++) {
// hack: use Canny instead of zero threshold level.
// Canny helps to catch squares with gradient shading
if (l == 0) {
// apply Canny. Take the upper threshold from slider
// and set the lower to 0 (which forces edges merging)
cvCanny(tgray, gray, 0, thresh, 5);
// dilate canny output to remove potential
// holes between edge segments
cvDilate(gray, gray, null, 1);
} else {
// apply threshold if l!=0:
// tgray(x,y) = gray(x,y) < (l+1)*255/N ? 255 : 0
cvThreshold(tgray, gray, (l+1)*255/N, 255, CV_THRESH_BINARY);
}
// find contours and store them all as a list
// Java translation: moved into the loop
CvSeq contours = new CvSeq();
cvFindContours(gray, storage, contours, Loader.sizeof(CvContour.class), CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE, cvPoint(0,0));
// test each contour
while (contours != null && !contours.isNull()) {
// approximate contour with accuracy proportional
// to the contour perimeter
// Java translation: moved into the loop
CvSeq result = cvApproxPoly(contours, Loader.sizeof(CvContour.class), storage, CV_POLY_APPROX_DP, cvContourPerimeter(contours)*0.02, 0);
// square contours should have 4 vertices after approximation
// relatively large area (to filter out noisy contours)
// and be convex.
// Note: absolute value of an area is used because
// area may be positive or negative - in accordance with the
// contour orientation
if(result.total() == 4 && Math.abs(cvContourArea(result, CV_WHOLE_SEQ, 0)) > 1000 && cvCheckContourConvexity(result) != 0) {
// Java translation: moved into loop
double s = 0.0, t = 0.0;
for( i = 0; i < 5; i++ ) {
// find minimum angle between joint
// edges (maximum of cosine)
if( i >= 2 ) {
// Java translation:
// Comment from the HoughLines.java sample code:
// " Based on JavaCPP, the equivalent of the C code:
// CvPoint* line = (CvPoint*)cvGetSeqElem(lines,i);
// CvPoint first=line[0];
// CvPoint second=line[1];
// is:
// Pointer line = cvGetSeqElem(lines, i);
// CvPoint first = new CvPoint(line).position(0);
// CvPoint second = new CvPoint(line).position(1);
// "
// ... so after some trial and error this seem to work
// t = fabs(angle(
// (CvPoint*)cvGetSeqElem( result, i ),
// (CvPoint*)cvGetSeqElem( result, i-2 ),
// (CvPoint*)cvGetSeqElem( result, i-1 )));
t = Math.abs(angle(new CvPoint(cvGetSeqElem(result, i)),
new CvPoint(cvGetSeqElem(result, i-2)),
new CvPoint(cvGetSeqElem(result, i-1))));
s = s > t ? s : t;
}
}
// if cosines of all angles are small
// (all angles are ~90 degree) then write quandrange
// vertices to resultant sequence
if (s < 0.3)
for( i = 0; i < 4; i++ ) {
cvSeqPush(squares, cvGetSeqElem(result, i));
}
}
// take the next contour
contours = contours.h_next();
}
}
}
// release all the temporary images
cvReleaseImage(gray);
cvReleaseImage(pyr);
cvReleaseImage(tgray);
cvReleaseImage(timg);
return squares;
}
// the function draws all the squares in the image
void drawSquares(IplImage img, CvSeq squares) {
// Java translation: Here the code is somewhat different from the C version.
// I was unable to get straight forward CvPoint[] arrays
// working with "reader" and the "CV_READ_SEQ_ELEM".
// CvSeqReader reader = new CvSeqReader();
IplImage cpy = cvCloneImage(img);
int i = 0;
// Used by attempt 3
// Create a "super"-slice, consisting of the entire sequence of squares
CvSlice slice = new CvSlice(squares);
// initialize reader of the sequence
// cvStartReadSeq(squares, reader, 0);
// read 4 sequence elements at a time (all vertices of a square)
for(i = 0; i < squares.total(); i += 4) {
// // Attempt 1:
// // This does not work, uses the "reader"
// CvPoint pt[] = new CvPoint[]{new CvPoint(1), new CvPoint(1), new CvPoint(1), new CvPoint(1)};
// PointerPointer rect = new PointerPointer(pt);
// int count[] = new int[]{4};
//
// CV_READ_SEQ_ELEM(pt[0], reader);
// CV_READ_SEQ_ELEM(pt[1], reader);
// CV_READ_SEQ_ELEM(pt[2], reader);
// CV_READ_SEQ_ELEM(pt[3], reader);
// // Attempt 2:
// // This works, somewhat similar to the C code, somewhat messy, does not use the "reader"
// CvPoint pt[] = new CvPoint[]{
// new CvPoint(cvGetSeqElem(squares, i)),
// new CvPoint(cvGetSeqElem(squares, i + 1)),
// new CvPoint(cvGetSeqElem(squares, i + 2)),
// new CvPoint(cvGetSeqElem(squares, i + 3))};
// PointerPointer rect = new PointerPointer(pt);
// int count[] = new int[]{4};
// Attempt 3:
// This works, may be the "cleanest" solution, does not use the "reader"
CvPoint rect = new CvPoint(4);
IntPointer count = new IntPointer(1).put(4);
// get the 4 corner slice from the "super"-slice
cvCvtSeqToArray(squares, rect, slice.start_index(i).end_index(i + 4));
// // Attempt 4:
// // This works, and look the most like the original C code, uses the "reader"
// CvPoint rect = new CvPoint(4);
// int count[] = new int[]{4};
//
// // read 4 vertices
// CV_READ_SEQ_ELEM(rect.position(0), reader);
// CV_READ_SEQ_ELEM(rect.position(1), reader);
// CV_READ_SEQ_ELEM(rect.position(2), reader);
// CV_READ_SEQ_ELEM(rect.position(3), reader);
// draw the square as a closed polyline
// Java translation: gotcha (re-)setting the opening "position" of the CvPoint sequence thing
cvPolyLine(cpy, rect.position(0), count, 1, 1, CV_RGB(0,255,0), 3, CV_AA, 0);
}
// show the resultant image
// cvShowImage(wndname, cpy);
canvas.showImage(converter.convert(cpy));
cvReleaseImage(cpy);
}
String names[] = new String[]{ "pic1.png", "pic2.png", "pic3.png",
"pic4.png", "pic5.png", "pic6.png" };
public static void main(String args[]) throws Exception {
new Square().main();
}
public void main() throws InterruptedException {
// Java translation: c not used
int i; // , c;
// create memory storage that will contain all the dynamic data
storage = cvCreateMemStorage(0);
for(i = 0; i < names.length; i++) {
// load i-th image
// Java translation
String filePathAndName = Square.class.getClassLoader().getResource(names[i]).getPath();
filePathAndName = filePathAndName == null || filePathAndName.isEmpty() ? names[i] : filePathAndName;
// img0 = cvLoadImage(names[i], 1);
img0 = cvLoadImage(filePathAndName, 1);
if (img0 == null) {
System.err.println("Couldn't load " + names[i]);
continue;
}
img = cvCloneImage(img0);
// create window and a trackbar (slider) with parent "image" and set callback
// (the slider regulates upper threshold, passed to Canny edge detector)
// Java translation
canvas = new CanvasFrame(wndname, 1);
canvas.setDefaultCloseOperation(javax.swing.JFrame.EXIT_ON_CLOSE);
// cvNamedWindow( wndname, 1 );
// find and draw the squares
drawSquares(img, findSquares4(img, storage));
// wait for key.
// Also the function cvWaitKey takes care of event processing
// Java translation
KeyEvent key = canvas.waitKey(0);
// c = cvWaitKey(0);
// release both images
cvReleaseImage(img);
cvReleaseImage(img0);
// clear memory storage - reset free space position
cvClearMemStorage(storage);
if (key.getKeyCode() == 27) {
break;
}
}
// cvDestroyWindow( wndname );
if (canvas != null) {
canvas.dispose();
}
}
}