blob: b8e84c0b858588580d1c0aad21220c5f9c4e83cf [file] [log] [blame]
Brian Silverman967e5df2020-02-09 16:43:34 -08001#include <opencv2/features2d.hpp>
2#include <opencv2/imgproc.hpp>
3
Brian Silverman9dd793b2020-01-31 23:52:21 -08004#include "aos/events/shm_event_loop.h"
5#include "aos/init.h"
6
Brian Silverman967e5df2020-02-09 16:43:34 -08007#include "y2020/vision/sift/demo_sift.h"
8#include "y2020/vision/sift/sift971.h"
9#include "y2020/vision/sift/sift_generated.h"
10#include "y2020/vision/sift/sift_training_generated.h"
Brian Silverman9dd793b2020-01-31 23:52:21 -080011#include "y2020/vision/v4l2_reader.h"
Brian Silverman967e5df2020-02-09 16:43:34 -080012#include "y2020/vision/vision_generated.h"
Brian Silverman9dd793b2020-01-31 23:52:21 -080013
14namespace frc971 {
15namespace vision {
16namespace {
17
Brian Silverman967e5df2020-02-09 16:43:34 -080018class CameraReader {
19 public:
20 CameraReader(aos::EventLoop *event_loop,
21 const sift::TrainingData *training_data, V4L2Reader *reader,
22 cv::FlannBasedMatcher *matcher)
23 : event_loop_(event_loop),
24 training_data_(training_data),
25 reader_(reader),
26 matcher_(matcher),
27 image_sender_(event_loop->MakeSender<CameraImage>("/camera")),
28 result_sender_(
29 event_loop->MakeSender<sift::ImageMatchResult>("/camera")),
30 read_image_timer_(event_loop->AddTimer([this]() {
31 ReadImage();
32 read_image_timer_->Setup(event_loop_->monotonic_now());
33 })) {
34 CopyTrainingFeatures();
35 // Technically we don't need to do this, but doing it now avoids the first
36 // match attempt being slow.
37 matcher_->train();
38
39 event_loop->OnRun(
40 [this]() { read_image_timer_->Setup(event_loop_->monotonic_now()); });
41 }
42
43 private:
44 // Copies the information from training_data_ into matcher_.
45 void CopyTrainingFeatures();
46 // Processes an image (including sending the results).
47 void ProcessImage(const CameraImage &image);
48 // Reads an image, and then performs all of our processing on it.
49 void ReadImage();
50
51 flatbuffers::Offset<
52 flatbuffers::Vector<flatbuffers::Offset<sift::ImageMatch>>>
53 PackImageMatches(flatbuffers::FlatBufferBuilder *fbb,
54 const std::vector<std::vector<cv::DMatch>> &matches);
55 flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<sift::Feature>>>
56 PackFeatures(flatbuffers::FlatBufferBuilder *fbb,
57 const std::vector<cv::KeyPoint> &keypoints,
58 const cv::Mat &descriptors);
59
Brian Silverman4d4a70d2020-02-17 13:03:19 -080060 // Returns the 3D location for the specified training feature.
61 cv::Point3f Training3dPoint(int training_image_index, int feature_index) {
62 const sift::KeypointFieldLocation *const location =
63 training_data_->images()
64 ->Get(training_image_index)
65 ->features()
66 ->Get(feature_index)
67 ->field_location();
68 return cv::Point3f(location->x(), location->y(), location->z());
69 }
70
71 int number_training_images() const {
72 return training_data_->images()->size();
73 }
74
Brian Silverman967e5df2020-02-09 16:43:34 -080075 aos::EventLoop *const event_loop_;
76 const sift::TrainingData *const training_data_;
77 V4L2Reader *const reader_;
78 cv::FlannBasedMatcher *const matcher_;
79 aos::Sender<CameraImage> image_sender_;
80 aos::Sender<sift::ImageMatchResult> result_sender_;
81 // We schedule this immediately to read an image. Having it on a timer means
82 // other things can run on the event loop in between.
83 aos::TimerHandler *const read_image_timer_;
84
85 const std::unique_ptr<frc971::vision::SIFT971_Impl> sift_{
86 new frc971::vision::SIFT971_Impl()};
87};
88
89void CameraReader::CopyTrainingFeatures() {
90 for (const sift::TrainingImage *training_image : *training_data_->images()) {
91 cv::Mat features(training_image->features()->size(), 128, CV_32F);
92 for (size_t i = 0; i < training_image->features()->size(); ++i) {
93 const sift::Feature *feature_table = training_image->features()->Get(i);
94 const flatbuffers::Vector<float> *const descriptor =
95 feature_table->descriptor();
96 CHECK_EQ(descriptor->size(), 128u) << ": Unsupported feature size";
97 cv::Mat(1, descriptor->size(), CV_32F,
98 const_cast<void *>(static_cast<const void *>(descriptor->data())))
99 .copyTo(features(cv::Range(i, i + 1), cv::Range(0, 128)));
100 }
101 matcher_->add(features);
102 }
103}
104
105void CameraReader::ProcessImage(const CameraImage &image) {
106 // First, we need to extract the brightness information. This can't really be
107 // fused into the beginning of the SIFT algorithm because the algorithm needs
108 // to look at the base image directly. It also only takes 2ms on our images.
109 // This is converting from YUYV to a grayscale image.
110 cv::Mat image_mat(
111 image.rows(), image.cols(), CV_8U);
112 CHECK(image_mat.isContinuous());
113 const int number_pixels = image.rows() * image.cols();
114 for (int i = 0; i < number_pixels; ++i) {
115 reinterpret_cast<uint8_t *>(image_mat.data)[i] =
116 image.data()->data()[i * 2];
117 }
118
119 // Next, grab the features from the image.
120 std::vector<cv::KeyPoint> keypoints;
121 cv::Mat descriptors;
122 sift_->detectAndCompute(image_mat, cv::noArray(), keypoints, descriptors);
123
124 // Then, match those features against our training data.
125 std::vector<std::vector<cv::DMatch>> matches;
126 matcher_->knnMatch(/* queryDescriptors */ descriptors, matches, /* k */ 2);
127
128 // Now, pack the results up and send them out.
129 auto builder = result_sender_.MakeBuilder();
130
131 const auto image_matches_offset = PackImageMatches(builder.fbb(), matches);
132 // TODO(Brian): PackCameraPoses (and put it in the result)
133 const auto features_offset =
134 PackFeatures(builder.fbb(), keypoints, descriptors);
135
136 sift::ImageMatchResult::Builder result_builder(*builder.fbb());
137 result_builder.add_image_matches(image_matches_offset);
138 result_builder.add_features(features_offset);
139 result_builder.add_image_monotonic_timestamp_ns(
140 image.monotonic_timestamp_ns());
141 builder.Send(result_builder.Finish());
142}
143
144void CameraReader::ReadImage() {
145 if (!reader_->ReadLatestImage()) {
146 LOG(INFO) << "No image, sleeping";
147 std::this_thread::sleep_for(std::chrono::milliseconds(10));
148 return;
149 }
150
151 ProcessImage(reader_->LatestImage());
152
153 reader_->SendLatestImage();
154}
155
156flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<sift::ImageMatch>>>
157CameraReader::PackImageMatches(
158 flatbuffers::FlatBufferBuilder *fbb,
159 const std::vector<std::vector<cv::DMatch>> &matches) {
160 // First, we need to pull out all the matches for each image. Might as well
161 // build up the Match tables at the same time.
Brian Silverman4d4a70d2020-02-17 13:03:19 -0800162 std::vector<std::vector<sift::Match>> per_image_matches(
163 number_training_images());
Brian Silverman967e5df2020-02-09 16:43:34 -0800164 for (const std::vector<cv::DMatch> &image_matches : matches) {
165 for (const cv::DMatch &image_match : image_matches) {
Brian Silverman4d4a70d2020-02-17 13:03:19 -0800166 CHECK_LT(image_match.imgIdx, number_training_images());
167 per_image_matches[image_match.imgIdx].emplace_back();
168 sift::Match *const match = &per_image_matches[image_match.imgIdx].back();
169 match->mutate_query_feature(image_match.queryIdx);
170 match->mutate_train_feature(image_match.trainIdx);
171 match->mutate_distance(image_match.distance);
Brian Silverman967e5df2020-02-09 16:43:34 -0800172 }
173 }
174
175 // Then, we need to build up each ImageMatch table.
176 std::vector<flatbuffers::Offset<sift::ImageMatch>> image_match_tables;
177 for (size_t i = 0; i < per_image_matches.size(); ++i) {
Brian Silverman4d4a70d2020-02-17 13:03:19 -0800178 const std::vector<sift::Match> &this_image_matches = per_image_matches[i];
Brian Silverman967e5df2020-02-09 16:43:34 -0800179 if (this_image_matches.empty()) {
180 continue;
181 }
Brian Silverman4d4a70d2020-02-17 13:03:19 -0800182 const auto vector_offset = fbb->CreateVectorOfStructs(this_image_matches);
Brian Silverman967e5df2020-02-09 16:43:34 -0800183 sift::ImageMatch::Builder image_builder(*fbb);
184 image_builder.add_train_image(i);
185 image_builder.add_matches(vector_offset);
186 image_match_tables.emplace_back(image_builder.Finish());
187 }
188
189 return fbb->CreateVector(image_match_tables);
190}
191
192flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<sift::Feature>>>
193CameraReader::PackFeatures(flatbuffers::FlatBufferBuilder *fbb,
194 const std::vector<cv::KeyPoint> &keypoints,
195 const cv::Mat &descriptors) {
196 const int number_features = keypoints.size();
197 CHECK_EQ(descriptors.rows, number_features);
198 std::vector<flatbuffers::Offset<sift::Feature>> features_vector(
199 number_features);
200 for (int i = 0; i < number_features; ++i) {
201 const auto submat = descriptors(cv::Range(i, i + 1), cv::Range(0, 128));
202 CHECK(submat.isContinuous());
203 const auto descriptor_offset =
204 fbb->CreateVector(reinterpret_cast<float *>(submat.data), 128);
205 sift::Feature::Builder feature_builder(*fbb);
206 feature_builder.add_descriptor(descriptor_offset);
207 feature_builder.add_x(keypoints[i].pt.x);
208 feature_builder.add_y(keypoints[i].pt.y);
209 feature_builder.add_size(keypoints[i].size);
210 feature_builder.add_angle(keypoints[i].angle);
211 feature_builder.add_response(keypoints[i].response);
212 feature_builder.add_octave(keypoints[i].octave);
213 CHECK_EQ(-1, keypoints[i].class_id)
214 << ": Not sure what to do with a class id";
215 features_vector[i] = feature_builder.Finish();
216 }
217 return fbb->CreateVector(features_vector);
218}
219
Brian Silverman9dd793b2020-01-31 23:52:21 -0800220void CameraReaderMain() {
221 aos::FlatbufferDetachedBuffer<aos::Configuration> config =
222 aos::configuration::ReadConfig("config.json");
223
Brian Silverman967e5df2020-02-09 16:43:34 -0800224 const auto training_data_bfbs = DemoSiftData();
225 const sift::TrainingData *const training_data =
226 flatbuffers::GetRoot<sift::TrainingData>(training_data_bfbs.data());
227 {
228 flatbuffers::Verifier verifier(
229 reinterpret_cast<const uint8_t *>(training_data_bfbs.data()),
230 training_data_bfbs.size());
231 CHECK(training_data->Verify(verifier));
232 }
233
234 const auto index_params = cv::makePtr<cv::flann::IndexParams>();
235 index_params->setAlgorithm(cvflann::FLANN_INDEX_KDTREE);
236 index_params->setInt("trees", 5);
237 const auto search_params =
238 cv::makePtr<cv::flann::SearchParams>(/* checks */ 50);
239 cv::FlannBasedMatcher matcher(index_params, search_params);
240
Brian Silverman9dd793b2020-01-31 23:52:21 -0800241 aos::ShmEventLoop event_loop(&config.message());
242 V4L2Reader v4l2_reader(&event_loop, "/dev/video0");
Brian Silverman967e5df2020-02-09 16:43:34 -0800243 CameraReader camera_reader(&event_loop, training_data, &v4l2_reader, &matcher);
Brian Silverman9dd793b2020-01-31 23:52:21 -0800244
Brian Silverman967e5df2020-02-09 16:43:34 -0800245 event_loop.Run();
Brian Silverman9dd793b2020-01-31 23:52:21 -0800246}
247
248} // namespace
249} // namespace vision
250} // namespace frc971
251
252
253int main(int argc, char **argv) {
254 aos::InitGoogle(&argc, &argv);
255 frc971::vision::CameraReaderMain();
256}