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