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