Parker Schuh | d497ed6 | 2017-03-04 20:11:58 -0800 | [diff] [blame^] | 1 | #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 | |
| 27 | namespace y2017 { |
| 28 | namespace vision { |
| 29 | |
| 30 | using aos::events::TCPServer; |
| 31 | using aos::vision::DataRef; |
| 32 | using aos::vision::Int32Codec; |
| 33 | using aos::vision::ImageValue; |
| 34 | using aos::vision::Int64Codec; |
| 35 | using aos::events::TXUdpSocket; |
| 36 | using aos::events::DataSocket; |
| 37 | using aos::vision::ImageFormat; |
| 38 | using aos::vision::ImageStreamEvent; |
| 39 | |
| 40 | int64_t Nanos(aos::monotonic_clock::duration time_diff) { |
| 41 | return std::chrono::duration_cast<std::chrono::nanoseconds>(time_diff) |
| 42 | .count(); |
| 43 | } |
| 44 | |
| 45 | int64_t NowNanos() { |
| 46 | return Nanos(aos::monotonic_clock::now().time_since_epoch()); |
| 47 | } |
| 48 | |
| 49 | inline bool FileExist(const std::string &name) { |
| 50 | struct stat buffer; |
| 51 | return (stat(name.c_str(), &buffer) == 0); |
| 52 | } |
| 53 | |
| 54 | class 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 | |
| 79 | ImageFormat GetImageFormat(const CameraSettings ¶ms) { |
| 80 | return ImageFormat{params.width(), params.height()}; |
| 81 | } |
| 82 | |
| 83 | class 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 | |
| 184 | void 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 | |
| 203 | bool 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 | |
| 228 | int 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 | } |