001/**
002 * Copyright (c) 2011, The University of Southampton and the individual contributors.
003 * All rights reserved.
004 *
005 * Redistribution and use in source and binary forms, with or without modification,
006 * are permitted provided that the following conditions are met:
007 *
008 *   *  Redistributions of source code must retain the above copyright notice,
009 *      this list of conditions and the following disclaimer.
010 *
011 *   *  Redistributions in binary form must reproduce the above copyright notice,
012 *      this list of conditions and the following disclaimer in the documentation
013 *      and/or other materials provided with the distribution.
014 *
015 *   *  Neither the name of the University of Southampton nor the names of its
016 *      contributors may be used to endorse or promote products derived from this
017 *      software without specific prior written permission.
018 *
019 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
020 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
021 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
022 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
023 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
025 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
026 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
027 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
028 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
029 */
030package org.openimaj.image.camera.calibration;
031
032import gnu.trove.map.hash.TIntIntHashMap;
033import gnu.trove.map.hash.TObjectIntHashMap;
034
035import java.io.IOException;
036import java.net.MalformedURLException;
037import java.util.ArrayDeque;
038import java.util.ArrayList;
039import java.util.Collections;
040import java.util.Deque;
041import java.util.EnumSet;
042import java.util.HashMap;
043import java.util.List;
044import java.util.Map;
045
046import org.apache.log4j.Logger;
047import org.openimaj.algorithm.iterative.MinEpsilonOrMaxIterations;
048import org.openimaj.image.FImage;
049import org.openimaj.image.MBFImage;
050import org.openimaj.image.analyser.ImageAnalyser;
051import org.openimaj.image.colour.RGBColour;
052import org.openimaj.image.contour.Contour;
053import org.openimaj.image.contour.SuzukiContourProcessor;
054import org.openimaj.image.feature.local.interest.SubPixelCorners;
055import org.openimaj.image.processing.algorithm.EqualisationProcessor;
056import org.openimaj.image.processing.algorithm.MaxFilter;
057import org.openimaj.image.processing.algorithm.MeanCenter;
058import org.openimaj.image.processing.threshold.AdaptiveLocalThresholdMean;
059import org.openimaj.math.geometry.point.Point2d;
060import org.openimaj.math.geometry.point.Point2dImpl;
061import org.openimaj.math.geometry.shape.Circle;
062import org.openimaj.math.geometry.shape.PointList;
063import org.openimaj.math.geometry.shape.Polygon;
064import org.openimaj.math.geometry.shape.Rectangle;
065import org.openimaj.util.pair.FloatIntPair;
066import org.openimaj.video.VideoDisplay;
067import org.openimaj.video.VideoDisplayAdapter;
068import org.openimaj.video.capture.VideoCapture;
069
070/**
071 * This is a port/reworking of the OpenCV chessboard corner finder algorithm to
072 * OpenIMAJ. The key image-processing and geometry algorithms use the OpenIMAJ
073 * equivalents, but the code to extract the board and assess the correctness is
074 * purely based on a (slightly sanitised) port of the original OpenCV code.
075 * <p>
076 * This is improved variant of chessboard corner detection algorithm that uses a
077 * graph of connected quads. It is based on the code contributed by Vladimir
078 * Vezhnevets and Philip Gruebele. Here is the copyright notice from the
079 * original Vladimir's code:
080 * <p>
081 * The algorithms developed and implemented by Vezhnevets Vldimir aka Dead Moroz
082 * (vvp@graphics.cs.msu.ru) See <a
083 * href="http://graphics.cs.msu.su/en/research/calibration/opencv.html"
084 * >http://graphics.cs.msu.su/en/research/calibration/opencv.html</a> for
085 * detailed information.
086 * <p>
087 * Reliability additions and modifications made by Philip Gruebele.
088 * <p>
089 * Some further improvements for detection of partially occluded boards at
090 * non-ideal lighting conditions have been made by Alex Bovyrin and Kurt
091 * Kolonige.
092 * 
093 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
094 */
095public class ChessboardCornerFinder implements ImageAnalyser<FImage> {
096        private static final class Corner extends Point2dImpl {
097                int row; // Board row index
098                int count; // Number of neighbor corners
099                Corner[] neighbours = new Corner[4];
100
101                public Corner(Point2d point2d) {
102                        super(point2d);
103                }
104
105                FloatIntPair meanDist()
106                {
107                        float sum = 0;
108                        int n = 0;
109                        for (int i = 0; i < 4; i++)
110                        {
111                                if (neighbours[i] != null)
112                                {
113                                        final float dx = neighbours[i].x - x;
114                                        final float dy = neighbours[i].y - y;
115                                        sum += Math.sqrt(dx * dx + dy * dy);
116                                        n++;
117                                }
118                        }
119                        return new FloatIntPair(sum / Math.max(n, 1), n);
120                }
121        }
122
123        private static final class Quad {
124                /**
125                 * The group that the quad belongs
126                 */
127                int groupIdx = -1;
128
129                /**
130                 * The corners
131                 */
132                Corner[] corners = new Corner[4];
133
134                /**
135                 * Minimum edge length in pixels
136                 */
137                float minEdgeLength;
138
139                /**
140                 * Actual number of neighbours
141                 */
142                int count;
143
144                /**
145                 * The neighbours
146                 */
147                Quad[] neighbours = new Quad[4];
148
149                /**
150                 * has the quad been sorted?
151                 */
152                boolean ordered;
153                /**
154                 * The row position
155                 */
156                int row;
157                /**
158                 * The column position
159                 */
160                int col;
161        }
162
163        private static final Logger logger = Logger.getLogger(ChessboardCornerFinder.class);
164
165        private static final int MIN_DILATIONS = 0;
166        private static final int MAX_DILATIONS = 7;
167        private static final SubPixelCorners SUB_PIX_CORNERS = new SubPixelCorners(2, 2,
168                        new MinEpsilonOrMaxIterations(0.1, 15));
169
170        /**
171         * Should filtering be enabled
172         */
173        boolean filterQuads = true;
174
175        /**
176         * Minimum area of quad if filtering
177         */
178        private double minSize = 25;
179
180        /**
181         * Maximum approximation distance
182         */
183        private int maxApproxLevel = 7;
184
185        /**
186         * Pre-process with histogram equalisation
187         */
188        boolean histogramEqualise = false;
189
190        /**
191         * Should mean adaptive thresholding be used?
192         */
193        boolean adaptiveThreshold = false;
194
195        /**
196         * Set if a fast check for the pattern be performed to bail early
197         */
198        final FastChessboardDetector fastDetector;
199
200        /**
201         * Number of blocks across the pattern
202         */
203        private int patternWidth;
204
205        /**
206         * Number of blocks down the pattern
207         */
208        private int patternHeight;
209
210        /**
211         * Was the complete pattern found?
212         */
213        boolean found = false;
214
215        /**
216         * The final corners
217         */
218        private List<Point2dImpl> outCorners = new ArrayList<Point2dImpl>();
219
220        /**
221         * Options for controlling how the corner finder works
222         * 
223         * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
224         * 
225         */
226        public static enum Options {
227                /**
228                 * Apply filtering to remove unlikely quads from detection
229                 */
230                FILTER_QUADS,
231                /**
232                 * Pre-process the image by performing histogram equalisation
233                 */
234                HISTOGRAM_EQUALISE,
235                /**
236                 * Perform adaptive (mean local) thresholding, rather than global
237                 */
238                ADAPTIVE_THRESHOLD,
239                /**
240                 * Perform the fast check to detect a pattern and bail early
241                 */
242                FAST_CHECK;
243        }
244
245        /**
246         * Construct with the given pattern size and options set.
247         * 
248         * @param patternWidth
249         *            the pattern width
250         * @param patternHeight
251         *            the pattern height
252         * @param opts
253         *            the options
254         */
255        public ChessboardCornerFinder(int patternWidth, int patternHeight, Options... opts)
256        {
257                this.patternWidth = patternWidth;
258                this.patternHeight = patternHeight;
259
260                final EnumSet<Options> es = EnumSet.noneOf(Options.class);
261                for (final Options o : opts)
262                        es.add(o);
263
264                this.filterQuads = es.contains(Options.FILTER_QUADS);
265                this.histogramEqualise = es.contains(Options.HISTOGRAM_EQUALISE);
266                this.adaptiveThreshold = es.contains(Options.ADAPTIVE_THRESHOLD);
267
268                if (es.contains(Options.FAST_CHECK))
269                        fastDetector = new FastChessboardDetector(patternWidth, patternHeight);
270                else
271                        fastDetector = null;
272        }
273
274        @Override
275        public void analyseImage(FImage image) {
276                // reset state:
277                this.found = false;
278                this.outCorners.clear();
279
280                if (histogramEqualise)
281                        image = image.process(new EqualisationProcessor());
282
283                int prevSqrSize = 0;
284
285                if (fastDetector != null) {
286                        fastDetector.analyseImage(image);
287
288                        if (!fastDetector.chessboardDetected()) {
289                                return;
290                        }
291                }
292
293                for (int k = 0; k < 6; k++) {
294                        for (int dilations = MIN_DILATIONS; dilations < MAX_DILATIONS; dilations++) {
295                                if (found)
296                                        break;
297
298                                FImage threshImg;
299                                if (adaptiveThreshold) {
300                                        final int blockSize = (int) (Math.round(prevSqrSize == 0 ?
301                                                        Math.min(image.width, image.height) * (k % 2 == 0 ? 0.2 : 0.1) : prevSqrSize * 2) | 1);
302
303                                        // adaptive mean thresholding
304                                        threshImg = image.process(new AdaptiveLocalThresholdMean(blockSize, (k / 2) * 5));
305                                        if (dilations > 0) {
306                                                MaxFilter.filter(threshImg, dilations - 1);
307                                        }
308                                } else {
309                                        threshImg = image.clone().threshold(MeanCenter.patchMean(image.pixels) - 10f / 255f);
310                                        MaxFilter.filter(threshImg, dilations);
311                                }
312
313                                // draw a border to allow us to find quads that go off the edge
314                                // of the image
315                                threshImg.drawShape(new Rectangle(1, 1, threshImg.width - 3, threshImg.height - 3), 3, 1f);
316
317                                final List<Quad> quads = extractQuads(threshImg);
318
319                                // not found...
320                                if (quads.size() <= 0)
321                                        continue;
322
323                                findQuadNeighbours(quads);
324
325                                for (int groupIdx = 0;; groupIdx++)
326                                {
327                                        final List<Quad> quadGroup = findConnectedQuads(quads, groupIdx);
328                                        int count = quadGroup.size();
329
330                                        final int icount = count;
331                                        if (count == 0)
332                                                break;
333
334                                        // order the quad corners globally
335                                        // maybe delete or add some
336                                        logger.trace("Starting ordering of inner quads");
337                                        count = orderFoundConnectedQuads(quadGroup, quads);
338                                        logger.trace(String.format("Orig count: %d  After ordering: %d", icount, count));
339
340                                        if (count == 0)
341                                                continue; // haven't found inner quads
342
343                                        // If count is more than it should be, this will remove
344                                        // those quads which cause maximum deviation from a nice
345                                        // square pattern.
346                                        count = cleanFoundConnectedQuads(quadGroup);
347                                        logger.trace(String.format("Connected group: %d  orig count: %d cleaned: %d", groupIdx, icount,
348                                                        count));
349
350                                        final List<Corner> cornerGroup = new ArrayList<Corner>();
351                                        count = checkQuadGroup(quadGroup, cornerGroup);
352                                        logger.trace(String.format("Connected group: %d  count: %d  cleaned: %d", groupIdx, icount, count));
353
354                                        int n = count > 0 ? patternWidth * patternHeight : -count;
355                                        n = Math.min(n, patternWidth * patternHeight);
356                                        float sumDist = 0;
357                                        int total = 0;
358
359                                        for (int i = 0; i < n; i++)
360                                        {
361                                                final FloatIntPair pair = cornerGroup.get(i).meanDist();
362                                                final float avgi = pair.first;
363                                                final int ni = pair.second;
364                                                sumDist += avgi * ni;
365                                                total += ni;
366                                        }
367                                        prevSqrSize = Math.round(sumDist / Math.max(total, 1));
368
369                                        if (count > 0 || (outCorners.size() > 0 && -count > outCorners.size()))
370                                        {
371                                                // copy corners to output array
372                                                outCorners.clear();
373                                                for (int i = 0; i < n; i++)
374                                                        outCorners.add(new Point2dImpl(cornerGroup.get(i)));
375
376                                                if (count == patternWidth * patternHeight && checkBoardMonotony())
377                                                {
378                                                        found = true;
379                                                        break;
380                                                }
381                                        }
382                                } // grp
383                        } // dilations
384                } // k
385
386                if (found)
387                        found = checkBoardMonotony();
388
389                // check that none of the found corners is too close to the image
390                // boundary
391                if (found)
392                {
393                        final int BORDER = 8;
394                        int k;
395                        for (k = 0; k < patternWidth * patternHeight; k++)
396                        {
397                                if (outCorners.get(k).x <= BORDER || outCorners.get(k).x > image.width - BORDER ||
398                                                outCorners.get(k).y <= BORDER || outCorners.get(k).y > image.height - BORDER)
399                                        break;
400                        }
401
402                        found = k == patternWidth * patternHeight;
403                }
404
405                if (found && patternHeight % 2 == 0 && patternWidth % 2 == 0)
406                {
407                        final int lastRow = (patternHeight - 1) * patternWidth;
408                        final double dy0 = outCorners.get(lastRow).y - outCorners.get(0).y;
409                        if (dy0 < 0)
410                        {
411                                int i;
412                                final int n = patternWidth * patternHeight;
413                                for (i = 0; i < n / 2; i++)
414                                {
415                                        Collections.swap(outCorners, i, n - i - 1);
416                                }
417                        }
418                }
419
420                if (found) {
421                        outCorners = SUB_PIX_CORNERS.findSubPixCorners(image, outCorners);
422                }
423        }
424
425        /**
426         * Returns corners in clockwise order corners don't necessarily start at
427         * same position on quad (e.g., top left corner)
428         * 
429         * @param threshImg
430         *            the binary image
431         * @return the extracted Quads
432         */
433        private List<Quad> extractQuads(final FImage threshImg) {
434                // if filtering is enabled, we try to guess the board contour containing
435                // all the relevant quads
436                final TObjectIntHashMap<Contour> counters = new TObjectIntHashMap<Contour>();
437                // cornerList is the list of valid quads (prior to
438                // filtering out those with the wrong parent if applicable)
439                final List<Corner[]> cornerList = new ArrayList<Corner[]>();
440                final Map<Corner[], Contour> parentMapping = new HashMap<Corner[], Contour>();
441                Contour board = null;
442
443                // extract contours
444                final Contour contours = SuzukiContourProcessor.findContours(threshImg);
445
446                for (final Contour c : contours.contourIterable()) {
447                        final Rectangle bounds = c.calculateRegularBoundingBox();
448
449                        // skip small regions
450                        if (!(c.isHole() && bounds.width * bounds.height >= minSize))
451                                continue;
452
453                        // try and make an approximated polygon with 4 vertices
454                        Polygon p = null;
455                        for (int approxLevel = 1; approxLevel <= maxApproxLevel; approxLevel++) {
456                                p = c.reduceVertices(approxLevel);
457                                if (p.nVertices() == 4)
458                                        break;
459                        }
460
461                        // test polygon for correctness
462                        if (p != null && p.nVertices() == 4 && p.isConvex() && p.hasNoInnerPolygons()) {
463                                final double perimeter = p.calculatePerimeter();
464                                final double area = p.calculateArea();
465
466                                final Corner[] pt = new Corner[4];
467                                for (int i = 0; i < 4; i++)
468                                        pt[i] = new Corner(p.points.get(i));
469
470                                float dx = pt[0].x - pt[2].x;
471                                float dy = pt[0].y - pt[2].y;
472                                final double d1 = Math.sqrt(dx * dx + dy * dy);
473
474                                dx = pt[1].x - pt[3].x;
475                                dy = pt[1].y - pt[3].y;
476                                final double d2 = Math.sqrt(dx * dx + dy * dy);
477
478                                dx = pt[0].x - pt[1].x;
479                                dy = pt[0].y - pt[1].y;
480                                final double d3 = Math.sqrt(dx * dx + dy * dy);
481                                dx = pt[1].x - pt[2].x;
482                                dy = pt[1].y - pt[2].y;
483                                final double d4 = Math.sqrt(dx * dx + dy * dy);
484
485                                if (!filterQuads
486                                                ||
487                                                (d3 * 4 > d4 && d4 * 4 > d3 && d3 * d4 < area * 1.5 && area > minSize && d1 >= 0.15 * perimeter && d2 >= 0.15 * perimeter))
488                                {
489                                        final Contour parent = c.parent;
490                                        counters.adjustOrPutValue(parent, 1, 1);
491
492                                        // basic idea is that the board should have more holes in it
493                                        // than anything else in the scene
494                                        if (board == null || counters.get(board) < counters.get(parent))
495                                                board = parent;
496
497                                        cornerList.add(pt);
498                                        parentMapping.put(pt, c.parent);
499                                }
500                        }
501                }
502
503                final List<Quad> quads = new ArrayList<Quad>();
504                for (int i = 0; i < cornerList.size(); i++) {
505                        final Corner[] pts = cornerList.get(i);
506
507                        // reject regions that don't belong to the predicted board
508                        if (filterQuads && parentMapping.get(pts) != board)
509                                continue;
510
511                        final Quad q = new Quad();
512                        q.corners = pts;
513
514                        q.minEdgeLength = Float.MAX_VALUE;
515                        for (int j = 0; j < 4; j++) {
516                                final float dx = pts[j].x - pts[(j + 1) & 3].x;
517                                final float dy = pts[j].y - pts[(j + 1) & 3].y;
518                                final float d = dx * dx + dy * dy;
519                                if (q.minEdgeLength > d)
520                                        q.minEdgeLength = d;
521                        }
522
523                        quads.add(q);
524                }
525                return quads;
526        }
527
528        /**
529         * Find the neighbouring quads for each quad
530         * 
531         * @param quads
532         *            the quads
533         */
534        private void findQuadNeighbours(List<Quad> quads)
535        {
536                final int quadCount = quads.size();
537                final float threshScale = 1.f;
538
539                // find quad neighbours
540                for (int idx = 0; idx < quads.size(); idx++)
541                {
542                        final Quad curQuad = quads.get(idx);
543
544                        // choose the points of the current quadrangle that are close to
545                        // some points of the other quadrangles
546                        // (it can happen for split corners (due to dilation) of the
547                        // checker board). Search only in other quadrangles!
548
549                        // for each corner of this quadrangle
550                        for (int i = 0; i < 4; i++)
551                        {
552                                float minDist = Float.MAX_VALUE;
553                                int closestCornerIdx = -1;
554                                Quad closestQuad = null;
555                                Corner closestCorner = null;
556
557                                if (curQuad.neighbours[i] != null)
558                                        continue;
559
560                                final Corner pt = curQuad.corners[i];
561
562                                float dx;
563                                float dy;
564                                float dist;
565                                // find the closest corner in all other quadrangles
566                                for (int k = 0; k < quadCount; k++)
567                                {
568                                        if (k == idx)
569                                                continue;
570
571                                        for (int j = 0; j < 4; j++)
572                                        {
573                                                if (quads.get(k).neighbours[j] != null)
574                                                        continue;
575
576                                                dx = pt.x - quads.get(k).corners[j].x;
577                                                dy = pt.y - quads.get(k).corners[j].y;
578                                                dist = dx * dx + dy * dy;
579
580                                                if (dist < minDist &&
581                                                                dist <= curQuad.minEdgeLength * threshScale &&
582                                                                dist <= quads.get(k).minEdgeLength * threshScale)
583                                                {
584                                                        // check edge lengths, make sure they're compatible
585                                                        // edges that are different by more than 1:4 are
586                                                        // rejected
587                                                        final float ediff = curQuad.minEdgeLength - quads.get(k).minEdgeLength;
588                                                        if (ediff > 32 * curQuad.minEdgeLength ||
589                                                                        ediff > 32 * quads.get(k).minEdgeLength)
590                                                        {
591                                                                logger.trace("Incompatible edge lengths");
592                                                                continue;
593                                                        }
594                                                        closestCornerIdx = j;
595                                                        closestQuad = quads.get(k);
596                                                        minDist = dist;
597                                                }
598                                        }
599                                }
600
601                                // we found a matching corner point?
602                                if (closestCornerIdx >= 0 && minDist < Float.MAX_VALUE)
603                                {
604                                        // If another point from our current quad is closer to the
605                                        // found corner
606                                        // than the current one, then we don't count this one after
607                                        // all.
608                                        // This is necessary to support small squares where
609                                        // otherwise the wrong
610                                        // corner will get matched to closest_quad;
611                                        closestCorner = closestQuad.corners[closestCornerIdx];
612
613                                        int j;
614                                        for (j = 0; j < 4; j++)
615                                        {
616                                                if (curQuad.neighbours[j] == closestQuad)
617                                                        break;
618
619                                                dx = closestCorner.x - curQuad.corners[j].x;
620                                                dy = closestCorner.y - curQuad.corners[j].y;
621
622                                                if (dx * dx + dy * dy < minDist)
623                                                        break;
624                                        }
625
626                                        if (j < 4 || curQuad.count >= 4 || closestQuad.count >= 4)
627                                                continue;
628
629                                        // Check that each corner is a neighbor of different quads
630                                        for (j = 0; j < closestQuad.count; j++)
631                                        {
632                                                if (closestQuad.neighbours[j] == curQuad)
633                                                        break;
634                                        }
635                                        if (j < closestQuad.count)
636                                                continue;
637
638                                        // check whether the closest corner to closestCorner
639                                        // is different from curQuad.corners[i].pt
640                                        int k;
641                                        for (k = 0; k < quadCount; k++)
642                                        {
643                                                final Quad q = quads.get(k);
644                                                if (k == idx || q == closestQuad)
645                                                        continue;
646
647                                                for (j = 0; j < 4; j++)
648                                                        if (q.neighbours[j] == null)
649                                                        {
650                                                                dx = closestCorner.x - q.corners[j].x;
651                                                                dy = closestCorner.y - q.corners[j].y;
652                                                                dist = dx * dx + dy * dy;
653                                                                if (dist < minDist)
654                                                                        break;
655                                                        }
656                                                if (j < 4)
657                                                        break;
658                                        }
659
660                                        if (k < quadCount)
661                                                continue;
662
663                                        closestCorner.x = (pt.x + closestCorner.x) * 0.5f;
664                                        closestCorner.y = (pt.y + closestCorner.y) * 0.5f;
665
666                                        // We've found one more corner - remember it
667                                        curQuad.count++;
668                                        curQuad.neighbours[i] = closestQuad;
669                                        curQuad.corners[i] = closestCorner;
670
671                                        closestQuad.count++;
672                                        closestQuad.neighbours[closestCornerIdx] = curQuad;
673                                }
674                        }
675                }
676        }
677
678        /**
679         * Was a chessboard found in the last call to {@link #analyseImage(FImage)}?
680         * 
681         * @return true if found; false otherwise
682         */
683        public boolean isFound() {
684                return found;
685        }
686
687        /**
688         * Get any corners detected by the last call to
689         * {@link #analyseImage(FImage)}.
690         * 
691         * @return the corners
692         */
693        public List<Point2dImpl> getCorners() {
694                return this.outCorners;
695        }
696
697        /**
698         * Find groups of connected quads. This searches for the first un-labelled
699         * quad and then finds the connected ones.
700         * 
701         * @param quads
702         *            the quads
703         * @param groupIdx
704         *            the group index
705         * @return the quads belonging to the group
706         */
707        private List<Quad> findConnectedQuads(List<Quad> quads, int groupIdx)
708        {
709                final Deque<Quad> stack = new ArrayDeque<Quad>();
710                int i;
711                final int quadCount = quads.size();
712                final List<Quad> outGroup = new ArrayList<Quad>();
713
714                // Scan the array for a first unlabeled quad
715                for (i = 0; i < quadCount; i++)
716                {
717                        if (quads.get(i).count > 0 && quads.get(i).groupIdx < 0)
718                                break;
719                }
720
721                // Recursively find a group of connected quads starting from the seed
722                // quad[i]
723                if (i < quadCount)
724                {
725                        Quad q = quads.get(i);
726                        stack.push(q);
727                        outGroup.add(q);
728                        q.groupIdx = groupIdx;
729                        q.ordered = false;
730
731                        while (stack.size() > 0)
732                        {
733                                q = stack.pop();
734                                for (i = 0; i < 4; i++)
735                                {
736                                        final Quad neighbour = q.neighbours[i];
737                                        if (neighbour != null && neighbour.count > 0 && neighbour.groupIdx < 0)
738                                        {
739                                                stack.push(neighbour);
740                                                outGroup.add(neighbour);
741                                                neighbour.groupIdx = groupIdx;
742                                                neighbour.ordered = false;
743                                        }
744                                }
745                        }
746                }
747
748                return outGroup;
749        }
750
751        /**
752         * order a group of connected quads order of corners: 0 is top left
753         * clockwise from there note: "top left" is nominal, depends on initial
754         * ordering of starting quad but all other quads are ordered consistently
755         * 
756         * can change the number of quads in the group can add quads, so we need to
757         * have quad/corner arrays passed in
758         */
759        private int orderFoundConnectedQuads(List<Quad> quads, List<Quad> allQuads)
760        {
761                final Deque<Quad> stack = new ArrayDeque<Quad>();
762
763                int quadCount = quads.size();
764
765                // first find an interior quad
766                Quad start = null;
767                for (int i = 0; i < quadCount; i++)
768                {
769                        if (quads.get(i).count == 4)
770                        {
771                                start = quads.get(i);
772                                break;
773                        }
774                }
775
776                if (start == null)
777                        return 0; // no 4-connected quad
778
779                // start with first one, assign rows/cols
780                int rowMin = 0, colMin = 0, rowMax = 0, colMax = 0;
781
782                final TIntIntHashMap colHist = new TIntIntHashMap();
783                final TIntIntHashMap rowHist = new TIntIntHashMap();
784
785                stack.push(start);
786                start.row = 0;
787                start.col = 0;
788                start.ordered = true;
789
790                // Recursively order the quads so that all position numbers (e.g.,
791                // 0,1,2,3) are in the at the same relative corner (e.g., lower right).
792
793                while (stack.size() > 0)
794                {
795                        final Quad q = stack.pop();
796                        int col = q.col;
797                        int row = q.row;
798                        colHist.adjustOrPutValue(col, 1, 1);
799                        rowHist.adjustOrPutValue(row, 1, 1);
800
801                        // check min/max
802                        if (row > rowMax)
803                                rowMax = row;
804                        if (row < rowMin)
805                                rowMin = row;
806                        if (col > colMax)
807                                colMax = col;
808                        if (col < colMin)
809                                colMin = col;
810
811                        for (int i = 0; i < 4; i++)
812                        {
813                                final Quad neighbour = q.neighbours[i];
814                                switch (i) // adjust col, row for this quad
815                                { // start at top left, go clockwise
816                                case 0:
817                                        row--;
818                                        col--;
819                                        break;
820                                case 1:
821                                        col += 2;
822                                        break;
823                                case 2:
824                                        row += 2;
825                                        break;
826                                case 3:
827                                        col -= 2;
828                                        break;
829                                }
830
831                                // just do inside quads
832                                if (neighbour != null && neighbour.ordered == false && neighbour.count == 4)
833                                {
834                                        orderQuad(neighbour, q.corners[i], (i + 2) % 4); // set in
835                                                                                                                                                // order
836                                        neighbour.ordered = true;
837                                        neighbour.row = row;
838                                        neighbour.col = col;
839                                        stack.push(neighbour);
840                                }
841                        }
842                }
843
844                // analyze inner quad structure
845                int w = patternWidth - 1;
846                int h = patternHeight - 1;
847                final int drow = rowMax - rowMin + 1;
848                final int dcol = colMax - colMin + 1;
849
850                // normalize pattern and found quad indices
851                if ((w > h && dcol < drow) ||
852                                (w < h && drow < dcol))
853                {
854                        h = patternWidth - 1;
855                        w = patternHeight - 1;
856                }
857
858                logger.trace(String.format("Size: %dx%d  Pattern: %dx%d", dcol, drow, w, h));
859
860                // check if there are enough inner quads
861                if (dcol < w || drow < h) // found enough inner quads?
862                {
863                        logger.trace("Too few inner quad rows/cols");
864                        return 0; // no, return
865                }
866
867                // check edges of inner quads
868                // if there is an outer quad missing, fill it in
869                // first order all inner quads
870                int found = 0;
871                for (int i = 0; i < quadCount; i++)
872                {
873                        if (quads.get(i).count == 4)
874                        { // ok, look at neighbours
875                                int col = quads.get(i).col;
876                                int row = quads.get(i).row;
877                                for (int j = 0; j < 4; j++)
878                                {
879                                        switch (j) // adjust col, row for this quad
880                                        { // start at top left, go clockwise
881                                        case 0:
882                                                row--;
883                                                col--;
884                                                break;
885                                        case 1:
886                                                col += 2;
887                                                break;
888                                        case 2:
889                                                row += 2;
890                                                break;
891                                        case 3:
892                                                col -= 2;
893                                                break;
894                                        }
895                                        final Quad neighbour = quads.get(i).neighbours[j];
896                                        if (neighbour != null && !neighbour.ordered && // is it an
897                                                                                                                                        // inner
898                                                                                                                                        // quad?
899                                                        col <= colMax && col >= colMin &&
900                                                        row <= rowMax && row >= rowMin)
901                                        {
902                                                // if so, set in order
903                                                logger.trace(String.format("Adding inner: col: %d  row: %d", col, row));
904                                                found++;
905                                                orderQuad(neighbour, quads.get(i).corners[j], (j + 2) % 4);
906                                                neighbour.ordered = true;
907                                                neighbour.row = row;
908                                                neighbour.col = col;
909                                        }
910                                }
911                        }
912                }
913
914                // if we have found inner quads, add corresponding outer quads,
915                // which are missing
916                if (found > 0)
917                {
918                        logger.trace(String.format("Found %d inner quads not connected to outer quads, repairing", found));
919                        for (int i = 0; i < quadCount; i++)
920                        {
921                                if (quads.get(i).count < 4 && quads.get(i).ordered)
922                                {
923                                        final int added = addOuterQuad(quads.get(i), quads, allQuads);
924                                        quadCount += added;
925                                }
926                        }
927                }
928
929                // final trimming of outer quads
930                if (dcol == w && drow == h) // found correct inner quads
931                {
932                        logger.trace("Inner bounds ok, check outer quads");
933                        int rcount = quadCount;
934                        for (int i = quadCount - 1; i >= 0; i--) // eliminate any quad not
935                                                                                                                // connected to
936                        // an ordered quad
937                        {
938                                if (quads.get(i).ordered == false)
939                                {
940                                        boolean outer = false;
941                                        for (int j = 0; j < 4; j++) // any neighbours that are
942                                                                                                // ordered?
943                                        {
944                                                if (quads.get(i).neighbours[j] != null && quads.get(i).neighbours[j].ordered)
945                                                        outer = true;
946                                        }
947                                        if (!outer) // not an outer quad, eliminate
948                                        {
949                                                logger.trace(String.format("Removing quad %d", i));
950                                                removeQuadFromGroup(quads, quads.get(i));
951                                                rcount--;
952                                        }
953                                }
954
955                        }
956                        return rcount;
957                }
958
959                return 0;
960        }
961
962        /**
963         * put quad into correct order, where <code>corner</code> has value
964         * <code<common</code>
965         * 
966         * @param quad
967         *            the quad to sort
968         * @param corner
969         *            the corner
970         * @param common
971         *            the common vertex
972         */
973        private void orderQuad(Quad quad, Corner corner, int common)
974        {
975                // find the corner
976                int tc;
977                for (tc = 0; tc < 4; tc++)
978                        if (quad.corners[tc].x == corner.x &&
979                                        quad.corners[tc].y == corner.y)
980                                break;
981
982                // set corner order
983                // shift
984                while (tc != common)
985                {
986                        // shift by one
987                        final Corner tempc = quad.corners[3];
988                        final Quad tempq = quad.neighbours[3];
989                        for (int i = 3; i > 0; i--)
990                        {
991                                quad.corners[i] = quad.corners[i - 1];
992                                quad.neighbours[i] = quad.neighbours[i - 1];
993                        }
994                        quad.corners[0] = tempc;
995                        quad.neighbours[0] = tempq;
996                        tc++;
997                        tc = tc % 4;
998                }
999        }
1000
1001        /*
1002         * add an outer quad
1003         * 
1004         * looks for the neighbor of <code>quad</code> that isn't present, tries to
1005         * add it in. <code>quad</code> is ordered
1006         * 
1007         * @param quad the quad
1008         * 
1009         * @param quads all quad group
1010         * 
1011         * @param allQuads all the quads
1012         * 
1013         * @return
1014         */
1015        private int addOuterQuad(final Quad quad, List<Quad> quads, List<Quad> allQuads)
1016        {
1017                int added = 0;
1018                for (int i = 0; i < 4; i++) // find no-neighbor corners
1019                {
1020                        if (quad.neighbours[i] == null) // ok, create and add neighbor
1021                        {
1022                                final int j = (i + 2) % 4;
1023                                logger.trace("Adding quad as neighbor 2");
1024                                final Quad q = new Quad();
1025                                allQuads.add(q);
1026                                added++;
1027                                quads.add(q);
1028
1029                                // set neighbor and group id
1030                                quad.neighbours[i] = q;
1031                                quad.count += 1;
1032                                q.neighbours[j] = quad;
1033                                q.groupIdx = quad.groupIdx;
1034                                q.count = 1; // number of neighbours
1035                                q.ordered = false;
1036                                q.minEdgeLength = quad.minEdgeLength;
1037
1038                                // make corners of new quad
1039                                // same as neighbor quad, but offset
1040                                Corner pt = quad.corners[i];
1041                                final float dx = pt.x - quad.corners[j].x;
1042                                final float dy = pt.y - quad.corners[j].y;
1043                                for (int k = 0; k < 4; k++)
1044                                {
1045                                        final Corner corner = new Corner(pt);
1046                                        pt = quad.corners[k];
1047                                        q.corners[k] = corner;
1048                                        corner.x += dx;
1049                                        corner.y += dy;
1050                                }
1051                                // have to set exact corner
1052                                q.corners[j] = quad.corners[i];
1053
1054                                // now find other neighbor and add it, if possible
1055                                if (quad.neighbours[(i + 3) % 4] != null &&
1056                                                quad.neighbours[(i + 3) % 4].ordered &&
1057                                                quad.neighbours[(i + 3) % 4].neighbours[i] != null &&
1058                                                quad.neighbours[(i + 3) % 4].neighbours[i].ordered)
1059                                {
1060                                        final Quad qn = quad.neighbours[(i + 3) % 4].neighbours[i];
1061                                        q.count = 2;
1062                                        q.neighbours[(j + 1) % 4] = qn;
1063                                        qn.neighbours[(i + 1) % 4] = q;
1064                                        qn.count += 1;
1065                                        // have to set exact corner
1066                                        q.corners[(j + 1) % 4] = qn.corners[(i + 1) % 4];
1067                                }
1068                        }
1069                }
1070                return added;
1071        }
1072
1073        /**
1074         * remove quad from quad group
1075         */
1076        private void removeQuadFromGroup(final List<Quad> quads, Quad q0)
1077        {
1078                final int count = quads.size();
1079                // remove any references to this quad as a neighbor
1080                for (int i = 0; i < count; i++)
1081                {
1082                        final Quad q = quads.get(i);
1083                        for (int j = 0; j < 4; j++)
1084                        {
1085                                if (q.neighbours[j] == q0)
1086                                {
1087                                        q.neighbours[j] = null;
1088                                        q.count--;
1089                                        for (int k = 0; k < 4; k++)
1090                                                if (q0.neighbours[k] == q)
1091                                                {
1092                                                        q0.neighbours[k] = null;
1093                                                        q0.count--;
1094                                                        break;
1095                                                }
1096                                        break;
1097                                }
1098                        }
1099                }
1100
1101                quads.remove(q0);
1102        }
1103
1104        /**
1105         * if we found too many connect quads, remove those which probably do not
1106         * belong.
1107         * 
1108         * @param quadGroup
1109         *            the group of quads
1110         * @return the new group size
1111         */
1112        private int cleanFoundConnectedQuads(List<Quad> quadGroup)
1113        {
1114                int quadCount = quadGroup.size();
1115                final Point2dImpl center = new Point2dImpl();
1116
1117                // number of quads this pattern should contain
1118                final int count = ((patternWidth + 1) * (patternHeight + 1) + 1) / 2;
1119
1120                // if we have more quadrangles than we should,
1121                // try to eliminate duplicates or ones which don't belong to the pattern
1122                // rectangle...
1123                if (quadCount <= count)
1124                        return quadCount;
1125
1126                // create an array of quadrangle centers
1127                final List<Point2dImpl> centers = new ArrayList<Point2dImpl>(quadCount);
1128
1129                for (int i = 0; i < quadCount; i++)
1130                {
1131                        final Point2dImpl ci = new Point2dImpl();
1132                        final Quad q = quadGroup.get(i);
1133
1134                        for (int j = 0; j < 4; j++)
1135                        {
1136                                final Point2dImpl pt = q.corners[j];
1137                                ci.x += pt.x;
1138                                ci.y += pt.y;
1139                        }
1140
1141                        ci.x *= 0.25f;
1142                        ci.y *= 0.25f;
1143
1144                        centers.add(ci);
1145                        center.x += ci.x;
1146                        center.y += ci.y;
1147                }
1148                center.x /= quadCount;
1149                center.y /= quadCount;
1150
1151                // If we still have more quadrangles than we should,
1152                // we try to eliminate bad ones based on minimizing the bounding box.
1153                // We iteratively remove the point which reduces the size of
1154                // the bounding box of the blobs the most
1155                // (since we want the rectangle to be as small as possible)
1156                // remove the quadrange that causes the biggest reduction
1157                // in pattern size until we have the correct number
1158                for (; quadCount > count; quadCount--)
1159                {
1160                        double minBoxArea = Double.MAX_VALUE;
1161                        int minBoxAreaIndex = -1;
1162                        Quad q0, q;
1163
1164                        // For each point, calculate box area without that point
1165                        for (int skip = 0; skip < quadCount; skip++)
1166                        {
1167                                // get bounding rectangle
1168                                final Point2dImpl temp = centers.get(skip); // temporarily make
1169                                                                                                                        // index 'skip' the
1170                                                                                                                        // same as
1171                                centers.set(skip, center); // pattern center (so it is not
1172                                                                                        // counted for convex hull)
1173                                final PointList pl = new PointList(centers);
1174                                final Polygon hull = pl.calculateConvexHull();
1175                                centers.set(skip, temp);
1176                                final double hullArea = hull.calculateArea();
1177
1178                                // remember smallest box area
1179                                if (hullArea < minBoxArea)
1180                                {
1181                                        minBoxArea = hullArea;
1182                                        minBoxAreaIndex = skip;
1183                                }
1184                        }
1185
1186                        q0 = quadGroup.get(minBoxAreaIndex);
1187
1188                        // remove any references to this quad as a neighbor
1189                        for (int i = 0; i < quadCount; i++)
1190                        {
1191                                q = quadGroup.get(i);
1192                                for (int j = 0; j < 4; j++)
1193                                {
1194                                        if (q.neighbours[j] == q0)
1195                                        {
1196                                                q.neighbours[j] = null;
1197                                                q.count--;
1198                                                for (int k = 0; k < 4; k++)
1199                                                        if (q0.neighbours[k] == q)
1200                                                        {
1201                                                                q0.neighbours[k] = null;
1202                                                                q0.count--;
1203                                                                break;
1204                                                        }
1205                                                break;
1206                                        }
1207                                }
1208                        }
1209
1210                        // remove the quad
1211                        quadCount--;
1212                        quadGroup.remove(minBoxAreaIndex);
1213                        centers.remove(minBoxAreaIndex);
1214                }
1215
1216                return quadCount;
1217        }
1218
1219        /**
1220         * 
1221         */
1222        private int checkQuadGroup(List<Quad> quadGroup, List<Corner> outCorners)
1223        {
1224                final int ROW1 = 1000000;
1225                final int ROW2 = 2000000;
1226                final int ROW_ = 3000000;
1227                int result = 0;
1228                final int quadCount = quadGroup.size();
1229                int cornerCount = 0;
1230                final Corner[] corners = new Corner[quadCount * 4];
1231
1232                int width = 0, height = 0;
1233                final int[] hist = { 0, 0, 0, 0, 0 };
1234                Corner first = null, first2 = null, right, cur, below, c;
1235
1236                try {
1237                        // build dual graph, which vertices are internal quad corners
1238                        // and two vertices are connected iff they lie on the same quad edge
1239                        for (int i = 0; i < quadCount; i++)
1240                        {
1241                                final Quad q = quadGroup.get(i);
1242
1243                                for (int j = 0; j < 4; j++)
1244                                {
1245                                        if (q.neighbours[j] != null)
1246                                        {
1247                                                final Corner a = q.corners[j], b = q.corners[(j + 1) & 3];
1248                                                // mark internal corners that belong to:
1249                                                // - a quad with a single neighbor - with ROW1,
1250                                                // - a quad with two neighbours - with ROW2
1251                                                // make the rest of internal corners with ROW_
1252                                                final int rowFlag = q.count == 1 ? ROW1 : q.count == 2 ? ROW2 : ROW_;
1253
1254                                                if (a.row == 0)
1255                                                {
1256                                                        corners[cornerCount++] = a;
1257                                                        a.row = rowFlag;
1258                                                }
1259                                                else if (a.row > rowFlag)
1260                                                        a.row = rowFlag;
1261
1262                                                if (q.neighbours[(j + 1) & 3] != null)
1263                                                {
1264                                                        if (a.count >= 4 || b.count >= 4)
1265                                                                throw new Exception();
1266                                                        for (int k = 0; k < 4; k++)
1267                                                        {
1268                                                                if (a.neighbours[k] == b)
1269                                                                        throw new Exception();
1270                                                                if (b.neighbours[k] == a)
1271                                                                        throw new Exception();
1272                                                        }
1273                                                        a.neighbours[a.count++] = b;
1274                                                        b.neighbours[b.count++] = a;
1275                                                }
1276                                        }
1277                                }
1278                        }
1279
1280                        if (cornerCount != patternWidth * patternHeight)
1281                                throw new Exception();
1282
1283                        for (int i = 0; i < cornerCount; i++)
1284                        {
1285                                final int n = corners[i].count;
1286                                assert (0 <= n && n <= 4);
1287                                hist[n]++;
1288                                if (first == null && n == 2)
1289                                {
1290                                        if (corners[i].row == ROW1)
1291                                                first = corners[i];
1292                                        else if (first2 == null && corners[i].row == ROW2)
1293                                                first2 = corners[i];
1294                                }
1295                        }
1296
1297                        // start with a corner that belongs to a quad with a signle
1298                        // neighbor.
1299                        // if we do not have such, start with a corner of a quad with two
1300                        // neighbours.
1301                        if (first == null)
1302                                first = first2;
1303
1304                        if (first == null || hist[0] != 0 || hist[1] != 0 || hist[2] != 4 ||
1305                                        hist[3] != (patternWidth + patternHeight) * 2 - 8)
1306                                throw new Exception();
1307
1308                        cur = first;
1309                        right = below = null;
1310                        outCorners.add(cur);
1311
1312                        for (int k = 0; k < 4; k++)
1313                        {
1314                                c = cur.neighbours[k];
1315                                if (c != null)
1316                                {
1317                                        if (right == null)
1318                                                right = c;
1319                                        else if (below == null)
1320                                                below = c;
1321                                }
1322                        }
1323
1324                        if (right == null || (right.count != 2 && right.count != 3) ||
1325                                        below == null || (below.count != 2 && below.count != 3))
1326                                throw new Exception();
1327
1328                        cur.row = 0;
1329                        first = below; // remember the first corner in the next row
1330                        // find and store the first row (or column)
1331                        while (true)
1332                        {
1333                                right.row = 0;
1334                                outCorners.add(right);
1335
1336                                if (right.count == 2)
1337                                        break;
1338                                if (right.count != 3 || outCorners.size() >= Math.max(patternWidth, patternHeight))
1339                                        throw new Exception();
1340                                cur = right;
1341
1342                                for (int k = 0; k < 4; k++)
1343                                {
1344                                        c = cur.neighbours[k];
1345                                        if (c != null && c.row > 0)
1346                                        {
1347                                                int kk;
1348                                                for (kk = 0; kk < 4; kk++)
1349                                                {
1350                                                        if (c.neighbours[kk] == below)
1351                                                                break;
1352                                                }
1353                                                if (kk < 4)
1354                                                        below = c;
1355                                                else
1356                                                        right = c;
1357                                        }
1358                                }
1359                        }
1360
1361                        width = outCorners.size();
1362                        if (width == patternWidth)
1363                                height = patternHeight;
1364                        else if (width == patternHeight)
1365                                height = patternWidth;
1366                        else
1367                                throw new Exception();
1368
1369                        // find and store all the other rows
1370                        for (int i = 1;; i++)
1371                        {
1372                                if (first == null)
1373                                        break;
1374                                cur = first;
1375                                first = null;
1376                                int j;
1377                                for (j = 0;; j++)
1378                                {
1379                                        cur.row = i;
1380                                        outCorners.add(cur);
1381                                        if (cur.count == 2 + (i < height - 1 ? 1 : 0) && j > 0)
1382                                                break;
1383
1384                                        right = null;
1385
1386                                        // find a neighbor that has not been processed yet
1387                                        // and that has a neighbor from the previous row
1388                                        for (int k = 0; k < 4; k++)
1389                                        {
1390                                                c = cur.neighbours[k];
1391                                                if (c != null && c.row > i)
1392                                                {
1393                                                        int kk;
1394                                                        for (kk = 0; kk < 4; kk++)
1395                                                        {
1396                                                                if (c.neighbours[kk] != null && c.neighbours[kk].row == i - 1)
1397                                                                        break;
1398                                                        }
1399                                                        if (kk < 4)
1400                                                        {
1401                                                                right = c;
1402                                                                if (j > 0)
1403                                                                        break;
1404                                                        }
1405                                                        else if (j == 0)
1406                                                                first = c;
1407                                                }
1408                                        }
1409                                        if (right == null)
1410                                                throw new Exception();
1411                                        cur = right;
1412                                }
1413
1414                                if (j != width - 1)
1415                                        throw new Exception();
1416                        }
1417
1418                        if (outCorners.size() != cornerCount)
1419                                throw new Exception();
1420
1421                        // check if we need to transpose the board
1422                        if (width != patternWidth)
1423                        {
1424                                final int t = width;
1425                                width = height;
1426                                height = t;
1427
1428                                for (int i = 0; i < cornerCount; i++)
1429                                        corners[i] = outCorners.get(i);
1430
1431                                for (int i = 0; i < height; i++)
1432                                        for (int j = 0; j < width; j++)
1433                                                outCorners.set(i * width + j, corners[j * height + i]);
1434                        }
1435
1436                        // check if we need to revert the order in each row
1437                        {
1438                                final Point2dImpl p0 = outCorners.get(0), p1 = outCorners.get(patternWidth - 1), p2 = outCorners
1439                                                .get(patternWidth);
1440                                if ((p1.x - p0.x) * (p2.y - p1.y) - (p1.y - p0.y) * (p2.x - p1.x) < 0)
1441                                {
1442                                        if (width % 2 == 0)
1443                                        {
1444                                                for (int i = 0; i < height; i++)
1445                                                        for (int j = 0; j < width / 2; j++)
1446                                                                Collections.swap(outCorners, i * width + j, i * width + width - j - 1);
1447                                        }
1448                                        else
1449                                        {
1450                                                for (int j = 0; j < width; j++)
1451                                                        for (int i = 0; i < height / 2; i++)
1452                                                                Collections.swap(outCorners, i * width + j, (height - i - 1) * width + j);
1453                                        }
1454                                }
1455                        }
1456
1457                        result = cornerCount;
1458
1459                } catch (final Exception ex) {
1460                        // ignore
1461                }
1462
1463                if (result <= 0)
1464                {
1465                        cornerCount = Math.min(cornerCount, patternWidth * patternHeight);
1466                        outCorners.clear();
1467                        for (int i = 0; i < cornerCount; i++)
1468                                outCorners.add(corners[i]);
1469                        result = -cornerCount;
1470
1471                        if (result == -patternWidth * patternHeight)
1472                                result = -result;
1473                }
1474
1475                return result;
1476        }
1477
1478        /**
1479         * Checks that each board row and column is pretty much monotonous curve:
1480         * 
1481         * It analyzes each row and each column of the chessboard as following:
1482         * 
1483         * for each corner c lying between end points in the same row/column it
1484         * checks that the point projection to the line segment (a,b) is lying
1485         * between projections of the neighbor corners in the same row/column.
1486         * 
1487         * This function has been created as temporary workaround for the bug in
1488         * current implementation of cvFindChessboardCornes that produces absolutely
1489         * unordered sets of corners.
1490         * 
1491         * @return true if the board is good; false otherwise.
1492         */
1493        private boolean checkBoardMonotony()
1494        {
1495                int i, j, k;
1496
1497                for (k = 0; k < 2; k++)
1498                {
1499                        for (i = 0; i < (k == 0 ? patternHeight : patternWidth); i++)
1500                        {
1501                                final Point2dImpl a = k == 0 ? outCorners.get(i * patternWidth) : outCorners.get(i);
1502                                final Point2dImpl b = k == 0 ? outCorners.get((i + 1) * patternWidth - 1) :
1503                                                outCorners.get((patternHeight - 1) * patternWidth + i);
1504                                float prevt = 0;
1505                                final float dx0 = b.x - a.x, dy0 = b.y - a.y;
1506                                if (Math.abs(dx0) + Math.abs(dy0) < Float.MIN_VALUE)
1507                                        return false;
1508                                for (j = 1; j < (k == 0 ? patternWidth : patternHeight) - 1; j++)
1509                                {
1510                                        final Point2dImpl c = k == 0 ? outCorners.get(i * patternWidth + j) :
1511                                                        outCorners.get(j * patternWidth + i);
1512                                        final float t = ((c.x - a.x) * dx0 + (c.y - a.y) * dy0) / (dx0 * dx0 + dy0 * dy0);
1513                                        if (t < prevt || t > 1)
1514                                                return false;
1515                                        prevt = t;
1516                                }
1517                        }
1518                }
1519
1520                return true;
1521        }
1522
1523        /**
1524         * Draw the predicted chessboard corners from the last call to
1525         * {@link #analyseImage(FImage)} on the given image.
1526         * 
1527         * @param image
1528         *            the image to draw on
1529         */
1530        public void drawChessboardCorners(MBFImage image) {
1531                drawChessboardCorners(image, patternWidth, patternHeight, outCorners, found);
1532        }
1533
1534        /**
1535         * Draw the given chessboard corners from on the given image.
1536         * 
1537         * @param image
1538         *            the image to draw on
1539         * @param patternWidth
1540         *            the chessboard pattern width
1541         * @param patternHeight
1542         *            the chessboard pattern height
1543         * @param corners
1544         *            the corners
1545         * @param found
1546         *            true if the corners were found
1547         */
1548        public static void drawChessboardCorners(MBFImage image, int patternWidth, int patternHeight,
1549                        List<? extends Point2d> corners, boolean found)
1550        {
1551                final int radius = 4;
1552
1553                if (!found) {
1554                        final Float[] color = RGBColour.RGB(0, 0, 255);
1555
1556                        for (int i = 0; i < corners.size(); i++)
1557                        {
1558                                final Point2d pt = corners.get(i);
1559                                image.drawLine(new Point2dImpl(pt.getX() - radius, pt.getY() - radius),
1560                                                new Point2dImpl(pt.getX() + radius, pt.getY() + radius), 1, color);
1561                                image.drawLine(new Point2dImpl(pt.getX() - radius, pt.getY() + radius),
1562                                                new Point2dImpl(pt.getX() + radius, pt.getY() - radius), 1, color);
1563                                image.drawShape(new Circle(pt, radius), 1, color);
1564                        }
1565                } else {
1566                        Point2d prevPt = new Point2dImpl();
1567                        final Float[][] lineColours =
1568                        {
1569                                        RGBColour.RGB(0, 0, 255),
1570                                        RGBColour.RGB(0, 128, 255),
1571                                        RGBColour.RGB(0, 200, 200),
1572                                        RGBColour.RGB(0, 255, 0),
1573                                        RGBColour.RGB(200, 200, 0),
1574                                        RGBColour.RGB(255, 0, 0),
1575                                        RGBColour.RGB(255, 0, 255)
1576                        };
1577
1578                        for (int y = 0, i = 0; y < patternHeight; y++) {
1579                                final Float[] color = lineColours[y % lineColours.length];
1580
1581                                for (int x = 0; x < patternWidth; x++, i++) {
1582                                        final Point2d pt = corners.get(i);
1583
1584                                        if (i != 0) {
1585                                                image.drawLine(prevPt, pt, 1, color);
1586                                        }
1587
1588                                        image.drawLine(new Point2dImpl(pt.getX() - radius, pt.getY() - radius),
1589                                                        new Point2dImpl(pt.getX() + radius, pt.getY() + radius), 1, color);
1590                                        image.drawLine(new Point2dImpl(pt.getX() - radius, pt.getY() + radius),
1591                                                        new Point2dImpl(pt.getX() + radius, pt.getY() - radius), 1, color);
1592                                        image.drawShape(new Circle(pt, radius), 1, color);
1593
1594                                        prevPt = pt;
1595                                }
1596                        }
1597                }
1598        }
1599
1600        /**
1601         * Simple test program
1602         * 
1603         * @param args
1604         * @throws MalformedURLException
1605         * @throws IOException
1606         */
1607        public static void main(String[] args) throws IOException {
1608                final ChessboardCornerFinder fcc = new ChessboardCornerFinder(9, 6,
1609                                Options.FILTER_QUADS, Options.FAST_CHECK);
1610                VideoDisplay.createVideoDisplay(new VideoCapture(640, 480)).addVideoListener(new VideoDisplayAdapter<MBFImage>()
1611                {
1612                        @Override
1613                        public void beforeUpdate(MBFImage frame) {
1614                                fcc.analyseImage(frame.flatten());
1615                                fcc.drawChessboardCorners(frame);
1616                        }
1617                });
1618        }
1619}