1 /*******************************************************************************
2 *   Copyright 2013-2014 EPFL                                                   *
3 *   Copyright 2013-2014 Quentin Bonnard                                        *
4 *                                                                              *
5 *   This file is part of chilitags.                                            *
6 *                                                                              *
7 *   Chilitags is free software: you can redistribute it and/or modify          *
8 *   it under the terms of the Lesser GNU General Public License as             *
9 *   published by the Free Software Foundation, either version 3 of the         *
10 *   License, or (at your option) any later version.                            *
11 *                                                                              *
12 *   Chilitags is distributed in the hope that it will be useful,               *
13 *   but WITHOUT ANY WARRANTY; without even the implied warranty of             *
14 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the              *
15 *   GNU Lesser General Public License for more details.                        *
16 *                                                                              *
17 *   You should have received a copy of the GNU Lesser General Public License   *
18 *   along with Chilitags.  If not, see <http://www.gnu.org/licenses/>.         *
19 *******************************************************************************/
20 
21 #include "ReadBits.hpp"
22 #include <opencv2/imgproc/imgproc.hpp>
23 
24 //#define DEBUG_ReadBits
25 #ifdef DEBUG_ReadBits
26 #include <opencv2/highgui/highgui.hpp>
27 #include <iostream>
28 #endif
29 
30 //std::round is missing in the Android NDK, we implement it here (taken from AOSP):
31 #ifdef ANDROID
32 namespace std {
33 template <typename T>
round(const T & x)34 inline T round(const T &x){
35     return static_cast<T>(std::floor(static_cast<float>(x) + 0.5f));
36 }
37 }
38 #endif
39 
40 namespace chilitags {
41 
42 static const int DATA_SIZE = 6;
43 static const int TAG_MARGIN = 2;
44 
ReadBits()45 ReadBits::ReadBits() :
46     mSamplePoints(),
47     mTransformedSamplePoints(DATA_SIZE*DATA_SIZE),
48     mSamples(1, DATA_SIZE*DATA_SIZE, CV_8U),
49     mBits(DATA_SIZE*DATA_SIZE)
50 {
51     for (int y = 0; y < DATA_SIZE; ++y)
52     {
53         for (int x = 0; x < DATA_SIZE; ++x)
54         {
55             mSamplePoints.push_back(cv::Point2f(
56                                         TAG_MARGIN + x + 0.5f,
57                                         TAG_MARGIN + y + 0.5f));
58         }
59     }
60 
61 #ifdef DEBUG_ReadBits
62     cv::namedWindow("ReadBits-full");
63     cv::namedWindow("ReadBits-tag");
64 #endif
65 }
66 
operator ()(const cv::Mat & inputImage,const Quad & corners)67 const std::vector<unsigned char>& ReadBits::operator()(const cv::Mat &inputImage, const Quad &corners)
68 {
69     static const float TAG_SIZE = 2*TAG_MARGIN+DATA_SIZE;
70     static const Quad NORMALIZED_CORNERS = {
71         0.f,      0.f,
72         TAG_SIZE,      0.f,
73         TAG_SIZE, TAG_SIZE,
74         0.f, TAG_SIZE
75     };
76 
77     cv::Mat_<cv::Point2f> cornersCopy(corners);
78 
79     // Sometimes, the corners are refined into a concave quadrilateral
80     // which makes ReadBits crash
81     cv::Mat convexHull;
82     cv::convexHull(cornersCopy, convexHull, true);
83     if (convexHull.rows != 4) {
84         cornersCopy = convexHull;
85         cornersCopy.push_back(0.5f*(cornersCopy(0)+cornersCopy(1)));
86     }
87 
88     auto roi = cv::boundingRect(cornersCopy);
89 
90     // Refine can actually provide corners outside the image
91     roi.x = cv::max(roi.x, 0);
92     roi.y = cv::max(roi.y, 0);
93     roi.width = cv::min(roi.width, inputImage.cols-roi.x);
94     roi.height = cv::min(roi.height, inputImage.rows-roi.y);
95 
96     cv::Point2f origin = roi.tl();
97     for (int i : {0,1,2,3}) cornersCopy(i) -= origin;
98 
99     cv::Matx33f transformation = cv::getPerspectiveTransform(NORMALIZED_CORNERS, cornersCopy);
100 
101     cv::perspectiveTransform(mSamplePoints, mTransformedSamplePoints, transformation);
102 
103     cv::Mat inputRoi = inputImage(roi);
104 
105     uchar* sampleData = mSamples.ptr(0);
106     for (auto& transformedSamplePoint : mTransformedSamplePoints) {
107         transformedSamplePoint.x = cv::max(cv::min(roi.width - 1, (int)std::round(transformedSamplePoint.x)), 0);
108         transformedSamplePoint.y = cv::max(cv::min(roi.height - 1, (int)std::round(transformedSamplePoint.y)), 0);
109 
110         *sampleData++ = inputRoi.at<uchar>(transformedSamplePoint);
111     }
112 
113     cv::threshold(mSamples, mBits, -1, 1, cv::THRESH_BINARY | cv::THRESH_OTSU);
114 
115 #ifdef DEBUG_ReadBits
116     for (int i = 0; i < DATA_SIZE; ++i)
117     {
118         for (int j = 0; j < DATA_SIZE; ++j)
119         {
120             std::cout << (int) mBits[i*DATA_SIZE + j];
121         }
122         std::cout << "\n";
123     }
124     std::cout << std::endl;
125 #define ZOOM_FACTOR 10
126 
127     cv::Mat debugImage = inputImage.clone();
128 
129     cv::Mat tag;
130     cv::resize(inputRoi, tag, cv::Size(0,0), ZOOM_FACTOR, ZOOM_FACTOR, cv::INTER_NEAREST);
131     cv::cvtColor(tag, tag, cv::COLOR_GRAY2BGR);
132 
133     for (int i = 0; i < DATA_SIZE; ++i)
134     {
135         for (int j = 0; j < DATA_SIZE; ++j)
136         {
137             cv::Point2f position = mTransformedSamplePoints[i*DATA_SIZE + j];
138             cv::circle(tag, position * ZOOM_FACTOR, 1,
139                        cv::Scalar(0,255,0),2);
140             cv::circle(tag, position * ZOOM_FACTOR, 3,
141                        cv::Scalar::all(mBits[i*DATA_SIZE + j]*255),2);
142         }
143     }
144 
145     cv::circle(tag, *cornersCopy[0] * ZOOM_FACTOR, 3, cv::Scalar(255,0,0),2);
146 
147     cv::line(tag, *cornersCopy[0]*ZOOM_FACTOR, *cornersCopy[1]*ZOOM_FACTOR,cv::Scalar(255,0,0));
148     cv::line(tag, *cornersCopy[1]*ZOOM_FACTOR, *cornersCopy[2]*ZOOM_FACTOR,cv::Scalar(255,0,255));
149     cv::line(tag, *cornersCopy[2]*ZOOM_FACTOR, *cornersCopy[3]*ZOOM_FACTOR,cv::Scalar(255,0,255));
150     cv::line(tag, *cornersCopy[3]*ZOOM_FACTOR, *cornersCopy[0]*ZOOM_FACTOR,cv::Scalar(255,0,255));
151 
152     cv::line(tag, *cornersCopy[2]*ZOOM_FACTOR, *cornersCopy[0]*ZOOM_FACTOR,cv::Scalar(255,0,255));
153     cv::line(tag, *cornersCopy[3]*ZOOM_FACTOR, *cornersCopy[1]*ZOOM_FACTOR,cv::Scalar(255,0,255));
154 
155     cv::line(debugImage, *cornersCopy[0]+origin, *cornersCopy[1]+origin,cv::Scalar(255,0,255));
156     cv::line(debugImage, *cornersCopy[1]+origin, *cornersCopy[2]+origin,cv::Scalar(255,0,255));
157     cv::line(debugImage, *cornersCopy[2]+origin, *cornersCopy[3]+origin,cv::Scalar(255,0,255));
158     cv::line(debugImage, *cornersCopy[3]+origin, *cornersCopy[0]+origin,cv::Scalar(255,0,255));
159 
160 
161     cv::imshow("ReadBits-full", debugImage);
162     cv::imshow("ReadBits-tag", tag);
163     cv::waitKey(0);
164 #endif
165 
166     return mBits;
167 }
168 
169 } /* namespace chilitags */
170