blob: 548d01df724d1e5f69e72da8de66e4552ab97bbd [file] [log] [blame]
Parker Schuhd497ed62017-03-04 20:11:58 -08001#include <google/protobuf/io/zero_copy_stream_impl.h>
2#include <google/protobuf/text_format.h>
3#include <stdio.h>
4#include <stdlib.h>
5#include <fstream>
6#include <iostream>
7#include <memory>
8#include <thread>
9#include <vector>
10
11#include "aos/common/logging/implementations.h"
12#include "aos/common/logging/logging.h"
13#include "aos/common/time.h"
14#include "aos/vision/blob/codec.h"
15#include "aos/vision/blob/find_blob.h"
16#include "aos/vision/blob/range_image.h"
17#include "aos/vision/blob/threshold.h"
18#include "aos/vision/events/socket_types.h"
19#include "aos/vision/events/udp.h"
20#include "aos/vision/image/image_stream.h"
21#include "aos/vision/image/jpeg_routines.h"
22#include "aos/vision/image/reader.h"
23#include "y2017/vision/target_finder.h"
24#include "y2017/vision/vision_config.pb.h"
25#include "y2017/vision/vision_result.pb.h"
26
27namespace y2017 {
28namespace vision {
29
30using aos::events::TCPServer;
31using aos::vision::DataRef;
32using aos::vision::Int32Codec;
33using aos::vision::ImageValue;
34using aos::vision::Int64Codec;
35using aos::events::TXUdpSocket;
36using aos::events::DataSocket;
37using aos::vision::ImageFormat;
38using aos::vision::ImageStreamEvent;
39
40int64_t Nanos(aos::monotonic_clock::duration time_diff) {
41 return std::chrono::duration_cast<std::chrono::nanoseconds>(time_diff)
42 .count();
43}
44
45int64_t NowNanos() {
46 return Nanos(aos::monotonic_clock::now().time_since_epoch());
47}
48
49inline bool FileExist(const std::string &name) {
50 struct stat buffer;
51 return (stat(name.c_str(), &buffer) == 0);
52}
53
54class BlobLog {
55 public:
56 explicit BlobLog(const char *prefix, const char *extension) {
57 int index = 0;
58 while (true) {
59 std::string file = prefix + std::to_string(index) + extension;
60 if (FileExist(file)) {
61 index++;
62 } else {
63 printf("Logging to file (%s)\n", file.c_str());
64 ofst_.open(file);
65 assert(ofst_.is_open());
66 break;
67 }
68 }
69 }
70
71 ~BlobLog() { ofst_.close(); }
72
73 void WriteLogEntry(DataRef data) { ofst_.write(&data[0], data.size()); }
74
75 private:
76 std::ofstream ofst_;
77};
78
79ImageFormat GetImageFormat(const CameraSettings &params) {
80 return ImageFormat{params.width(), params.height()};
81}
82
83class ImageSender : public ImageStreamEvent {
84 public:
85 ImageSender(camera::CameraParams params, GameSpecific game_cfg,
86 const std::string &fname, const std::string &ipadder, int port)
87 : ImageStreamEvent(fname, params),
88 game_cfg_(game_cfg),
89 udp_serv_(ipadder, 8080),
90 tcp_serv_(port),
91 log_("./logging/blob_record_", ".dat") {}
92
93 void WriteAndSendBlob(ImageFormat fmt, int64_t timestamp,
94 aos::vision::BlobList blobl) {
95 // Write blob to file for logging.
96 int blob_size = CalculateSize(blobl);
97 int tmp_size = blob_size + sizeof(int32_t) * 3 + sizeof(uint64_t);
98 char *buf;
99 std::string blob_data;
100 blob_data.resize(tmp_size, 0);
101 {
102 buf = Int32Codec::Write(&blob_data[0], tmp_size);
103 buf = Int64Codec::Write(buf, timestamp);
104 buf = Int32Codec::Write(buf, fmt.w);
105 buf = Int32Codec::Write(buf, fmt.h);
106 SerializeBlob(blobl, buf);
107 }
108 log_.WriteLogEntry(blob_data);
109
110 // Send the blob back to the debug-viewer
111 tcp_serv_.Broadcast([&](DataSocket *client) { client->Emit(blob_data); });
112 }
113
114 void ProcessImage(DataRef data, aos::monotonic_clock::time_point tp) {
115 using namespace aos::vision;
116 int64_t timestamp = std::chrono::duration_cast<std::chrono::nanoseconds>(
117 tp.time_since_epoch())
118 .count();
119 DecodeJpeg(data, &image_);
120 ImageFormat fmt = image_.fmt();
121
122 RangeImage rimg = finder_.Threshold(image_.get());
123 BlobList blobl = aos::vision::FindBlobs(rimg);
124
125 // Remove bad blobs before we log.
126 finder_.PreFilter(blobl);
127
128 // Write to the logi and stream to the debug-viewer.
129 WriteAndSendBlob(fmt, timestamp, blobl);
130
131 // Calculate each component.
132 std::vector<TargetComponent> target_component_list =
133 finder_.FillTargetComponentList(blobl);
134
135 // Put the compenents together into targets and pick the best.
136 y2017::vision::Target final_target;
137 bool found_target =
138 finder_.FindTargetFromComponents(target_component_list, &final_target);
139
140 std::string dat;
141 VisionResult result;
142 result.set_image_timestamp(timestamp);
143 result.set_send_timestamp(NowNanos());
144 if (found_target) {
145 result.mutable_target()->set_x(final_target.screen_coord.x());
146 result.mutable_target()->set_y(final_target.screen_coord.y());
147 }
148 // always send data
149 if (result.SerializeToString(&dat)) {
150 if (print_once_) {
151 printf("Beginning data streaming camera...\n");
152 print_once_ = false;
153 }
154
155 // Send only target over udp.
156 udp_serv_.Send(dat.data(), dat.size());
157 }
158 }
159
160 TCPServer<DataSocket> *GetTCPServ() { return &tcp_serv_; }
161
162 // Configuration related to the game.
163 GameSpecific game_cfg_;
164
165 // target selction code.
166 TargetFinder finder_;
167
168 // print when we start
169 bool print_once_ = true;
170
171 // udp socket on which to send to robot
172 TXUdpSocket udp_serv_;
173
174 // tcp socket on which to debug to laptop
175 TCPServer<DataSocket> tcp_serv_;
176
177 ImageValue image_;
178 BlobLog log_;
179 aos::monotonic_clock::time_point tstart;
180
181 private:
182};
183
184void RunCamera(CameraSettings settings, GameSpecific game_cfg,
185 const std::string &device, const std::string &ip_addr,
186 int port) {
187 printf("Creating camera (%dx%d).\n", settings.width(), settings.height());
188 camera::CameraParams params = {settings.width(),
189 settings.height(),
190 settings.exposure(),
191 settings.brightness(),
192 0,
193 (int32_t)settings.fps()};
194 ImageSender strm(params, game_cfg, device, ip_addr, port);
195
196 aos::events::EpollLoop loop;
197 loop.Add(strm.GetTCPServ());
198 loop.Add(&strm);
199 printf("Running Camera\n");
200 loop.Run();
201}
202
203bool ReadConfiguration(const std::string &file_name, VisionConfig *cfg) {
204 using namespace google::protobuf::io;
205 using namespace google::protobuf;
206 if (cfg == nullptr) {
207 return false;
208 }
209
210 // fd will close when it goes out of scope.
211 std::ifstream fd(file_name);
212 if (!fd.is_open()) {
213 fprintf(stderr, "File (%s) not found.\n", file_name.c_str());
214 return false;
215 }
216 IstreamInputStream is(&fd);
217 if (!TextFormat::Parse(&is, cfg)) {
218 fprintf(stderr, "Unable to parse file (%s).\n", file_name.c_str());
219 return false;
220 }
221
222 return true;
223}
224
225} // namespace vision
226} // namespace y2017
227
228int main(int, char **) {
229 using namespace y2017::vision;
230
231 ::aos::logging::Init();
232 ::aos::logging::AddImplementation(
233 new ::aos::logging::StreamLogImplementation(stdout));
234 VisionConfig cfg;
235 if (ReadConfiguration("ConfigFile.pb.ascii", &cfg)) {
236 if (cfg.robot_configs().count("Laptop") != 1) {
237 fprintf(stderr, "Could not find config key.\n");
238 return -1;
239 }
240 const RobotConfig &rbt = cfg.robot_configs().at("Laptop");
241 RunCamera(cfg.camera_params(), cfg.game_params(), rbt.camera_device_path(),
242 rbt.roborio_ipaddr(), rbt.port());
243 }
244
245 return EXIT_SUCCESS;
246}