From cb86c22f3db59a6581feee18e04370986c2a7745 Mon Sep 17 00:00:00 2001 From: kazu Date: Tue, 18 Apr 2017 20:35:48 +0200 Subject: [PATCH] latest version --- main.cpp | 33 +-- tex/chapters/experiments.tex | 44 +++- wifi/EvalWiFi.h | 408 +++++++++++++++++++++++++++++++++++ wifi/EvalWiFiPathMethods.h | 88 +++++--- 4 files changed, 529 insertions(+), 44 deletions(-) create mode 100644 wifi/EvalWiFi.h diff --git a/main.cpp b/main.cpp index 1f8a5ae..27c2d11 100644 --- a/main.cpp +++ b/main.cpp @@ -549,22 +549,22 @@ int main(void) { if (1 == 1) { std::vector files = { - Settings::path1a, //Settings::path1b, - //Settings::path2a, Settings::path2b, - //Settings::path_toni_all_1a, Settings::path_toni_all_1b, - //Settings::path_toni_all_2a, Settings::path_toni_all_2b, - //Settings::path_toni_inst_1a, Settings::path_toni_inst_1b, - //Settings::path_toni_inst_2a, Settings::path_toni_inst_2b, - //Settings::path_toni_inst_3a, Settings::path_toni_inst_3b, + Settings::path1a, Settings::path1b, + Settings::path2a, Settings::path2b, + Settings::path_toni_all_1a, Settings::path_toni_all_1b, + Settings::path_toni_all_2a, Settings::path_toni_all_2b, + Settings::path_toni_inst_1a, Settings::path_toni_inst_1b, + Settings::path_toni_inst_2a, Settings::path_toni_inst_2b, + Settings::path_toni_inst_3a, Settings::path_toni_inst_3b, }; std::vector> gtIndices = { - Settings::GroundTruth::path1, //Settings::GroundTruth::path1, - //Settings::GroundTruth::path2, Settings::GroundTruth::path2, - //Settings::GroundTruth::path_toni_all_1, Settings::GroundTruth::path_toni_all_1, - //Settings::GroundTruth::path_toni_all_2, Settings::GroundTruth::path_toni_all_2, - //Settings::GroundTruth::path_toni_inst_1, Settings::GroundTruth::path_toni_inst_1, - //Settings::GroundTruth::path_toni_inst_2, Settings::GroundTruth::path_toni_inst_2, - //Settings::GroundTruth::path_toni_inst_3, Settings::GroundTruth::path_toni_inst_3, + Settings::GroundTruth::path1, Settings::GroundTruth::path1, + Settings::GroundTruth::path2, Settings::GroundTruth::path2, + Settings::GroundTruth::path_toni_all_1, Settings::GroundTruth::path_toni_all_1, + Settings::GroundTruth::path_toni_all_2, Settings::GroundTruth::path_toni_all_2, + Settings::GroundTruth::path_toni_inst_1, Settings::GroundTruth::path_toni_inst_1, + Settings::GroundTruth::path_toni_inst_2, Settings::GroundTruth::path_toni_inst_2, + Settings::GroundTruth::path_toni_inst_3, Settings::GroundTruth::path_toni_inst_3, }; // EvalWiFiPaths ewp(Settings::fMap); @@ -583,7 +583,10 @@ int main(void) { EvalWiFiPathMethods ewpm(Settings::fMap); ewpm.loadModel(Settings::wifiEachOptParPos_perBBox, "model per region", "original", "alternative"); ewpm.walks(files, gtIndices); - ewpm.writeGP(Settings::fPathGFX, "normalVsExp"); + + // export for paper + // using errFuncOtherExponential and only path1a, path1b + //ewpm.writeGP(Settings::fPathGFX, "normalVsExp"); sleep(10000); diff --git a/tex/chapters/experiments.tex b/tex/chapters/experiments.tex index e3460f3..d7fe84f 100644 --- a/tex/chapters/experiments.tex +++ b/tex/chapters/experiments.tex @@ -74,10 +74,52 @@ kann man auch testen wenn man beim particle-filter das resampling ganz aus macht mit grafik: exp-dist vergroesert teils den abstand zu anderen locations , der GT selbst wird also besser, aber an anderen stellen geht dafür der fehler hoch und kann zu verlaufen führen (z.B. treppenhaus) } + + + + % -------------------------------- other distributions, unseen APs, etc -------------------------------- % + + To reduce the amount of misclassifications, where other locations within the building are (almost) + as likely (see \refeq{eq:wifiProb}) as the pedestrians actual location, we examined various + approaches. Unfortunately, none of which provided a viable enhancement under all conditions within + the performed walks. + + One possibility to dissolve an equal \docWIFI{}-likelihood between two (or more) locations within in the building + is, to not only consider the \docAPshort{}s seen by the Smartphone, but also the \docAPshort{}s not seen + by the Smartphone. Maybe there is an \docAP{} that should be visible at the other locations. However, + as the Smartphone did not see this \docAPshort{} the other location can be ruled out. + While this works in theory, evaluations revealed several issues: + + There is a chance that an \docAPshort{} is unseen during a scan due to packet collisions or + temporal effects within the surrounding. It thus might make sense to opt-out other locations + only, if at least two \docAPshort{}s are missing. On the other hand, this obviously requires (at least) + two \docAPshort{}s to actually be different between the two locations, which might not always be + the case. + + Also, this requires the signal strength prediction model to be fairly accurate. Within our testing + walks there are several places surrounded by concrete walls, which cause a harsh, local drop in signal strength. + The models used within this work will not accurately predict the signal strength for such locations. + Including \docAPshort{}s unseen by the Smartphone thus often increases the estimation error instead + of fixing the multimodality. + + We therefore examined variations of the probability calculation from \refeq{eq:wifiProb}. + Removing the strongest/weakest \docAPshort{} from $\mRssiVecWiFi{}$ yielded similar results. + While some estimations were improved, the overall estimation error increased for our walks, + as there are many situations where only a handful \docAP{}s can be seen. Removing (valid) + information will highly increase the error for such situations. + + Using a more strict exponential distribution for + \begin{figure} \input{gfx/wifiCompare_normalVsExp_cross.tex} \input{gfx/wifiCompare_normalVsExp_meter.tex} - \label{normal vs exponential} + \label{fig:normalVsExponential} + \caption{ + Comparison between normal- (black) and exponential-distribution (red) for \refeq{eq:wifiProb}. + While misclassifications are slightly reduced (upper chart), + the error between ground-truth and estimation (lower chart) increases by + about \SI{1}{\meter} for the median. + } \end{figure} \todo{ diff --git a/wifi/EvalWiFi.h b/wifi/EvalWiFi.h new file mode 100644 index 0000000..6bdef06 --- /dev/null +++ b/wifi/EvalWiFi.h @@ -0,0 +1,408 @@ +#ifndef EVALWIFI_H +#define EVALWIFI_H + +#include "Indoor/sensors/radio/setup/WiFiOptimizer.h" +#include "Indoor/sensors/radio/setup/WiFiFingerprint.h" +#include "Indoor/sensors/radio/setup/WiFiFingerprints.h" +#include "Indoor/sensors/radio/setup/WiFiOptimizer.h" +#include "Indoor/sensors/radio/model/WiFiModels.h" + +#include "Indoor/sensors/radio/VAPGrouper.h" +#include "Indoor/sensors/offline/FileReader.h" + +#include "Indoor/floorplan/v2/Floorplan.h" +#include "Indoor/floorplan/v2/FloorplanReader.h" +#include "Indoor/floorplan/v2/FloorplanHelper.h" +#include "Indoor/floorplan/v2/FloorplanCeilings.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "../Structs.h" +#include "../plots/Plotty.h" +#include "../plots/PlotErrTime.h" +#include "../plots/PlotErrFunc.h" +#include "../plots/PlotWiFiGroundProb.h" +//#include "CSV.h" + +#include +#include +#include "../Settings.h" + +template class Line { + +private: + + std::vector elements; + +public: + + void add(const T& elem) { + elements.push_back(elem); + } + + std::vector getAverage(const int size) { + std::vector res; + for (int i = 0; i < (int)elements.size(); ++i) { + + T sum; + int cnt = 0; + + // calculate sume of all elements around i + for (int j = -size; j <= +size; ++j) { + int idx = i+j; + if (idx < 0) {continue;} + if (idx >= elements.size()) {continue;} + sum += elements[idx]; + ++cnt; + } + + // calculate average + T avg = sum / cnt; + res.push_back(avg); + + } + return res; + } + +}; + +/** + * read path + * fetch wifi + * use given model to estimate the most likely location + * -> WIFI ONLY + */ +class EvalWiFi { + +private: + + Floorplan::IndoorMap* map; + BBox3 mapBBox; + + //WiFiFingerprints* calib; + VAPGrouper* vap = nullptr; + //WiFiOptimizer::LogDistCeiling* opt; + + Offline::FileReader reader; + WiFiModel* wiModel = nullptr; + std::vector gtIndices; + + // error in meter + PlotErrFunc* pef; + PlotErrTime* pet; + MovingAVG mavgMeter = MovingAVG(10); + + // error in probability + PlotErrFunc* pef2; + PlotErrTime* pet2; + + Plotty* plot; + + PlotWiFiGroundProb* groundProb; + +public: + + /** ctor with map and fingerprints */ + EvalWiFi(const std::string& mapFile, const std::string& fPath, const std::vector gtIndices) : reader(fPath), gtIndices(gtIndices) { + + std::cout << "EvalWiFi for " << fPath << std::endl; + + // load floorplan + map = Floorplan::Reader::readFromFile(mapFile); + + // estimate bbox + mapBBox = FloorplanHelper::getBBox(map); + +// // how to handle VAPs +// vap = new VAPGrouper(VAPGrouper::Mode::LAST_MAC_DIGIT_TO_ZERO, VAPGrouper::Aggregation::AVERAGE); + + // the optimizer + // opt = new WiFiOptimizer::LogDistCeiling(map, *vap, *calib, WiFiOptimizer::LogDistCeiling::Mode::MEDIUM); + + // how to handle VAPs + vap = new VAPGrouper(VAPGrouper::Mode::LAST_MAC_DIGIT_TO_ZERO, VAPGrouper::Aggregation::AVERAGE); + + pef = new PlotErrFunc("\\small{error (m)}", "\\small{measurements (\\%)}"); + pef->showMarkers(false); + + pef2 = new PlotErrFunc("\\small{-log(p(..))}", "\\small{measurements (\\%)}"); + pef2->showMarkers(false); + + pet = new PlotErrTime("walktime (seconds)", "\\small{error (m)}", ""); + pet->getPlot().getAxisY().setRange(K::GnuplotAxis::Range(0, 25)); + pet->getPlot().getKey().setVisible(true); + + pet2 = new PlotErrTime("walktime (seconds)", "\\small{-log(p(groundTruth | measurements))", ""); + + plot = new Plotty(map); + plot->buildFloorplan(); + plot->setGroundTruth(gtIndices); + + } + + void load(const std::string& xmlFile, const std::string& name) { + + WiFiModelFactory fac(map); + + // setup the model + this->wiModel = fac.loadXML(xmlFile); + + // fire + build(name); + + + } + + void writeTeX(const std::string& name) { + + pet->writeEpsTex(Settings::fPathGFX + "/wifi_eval_" + name + ".tex", K::GnuplotSize(8.6, 3.8)); + pet->writeCodeTo(Settings::fPathGFX + "/wifi_eval_" + name + ".gp"); + pet->plot(); + + } + + +private: + + void build(const std::string& name) { + + K::GnuplotStroke stroke( + K::GnuplotDashtype::SOLID, 1.5, + K::GnuplotColor::AUTO() + ); + static int idx = -1; ++idx; + + + const Offline::FileReader::GroundTruth gtp = reader.getGroundTruth(map, gtIndices); + + Line path; + K::GnuplotSplotElementLines* gpPath = new K::GnuplotSplotElementLines(); + gpPath->setStroke(stroke); + plot->splot.add(gpPath); + + K::Statistics* stats = new K::Statistics(); + K::Statistics* statsProbOnGT = new K::Statistics(); + pef->add(name, stats); + pef2->add(name, statsProbOnGT); + + // process each wifi entry within the offline file + for (const auto wifi : reader.wifi) { + + // all seen APs at one timestamp + const WiFiMeasurements& _mes = wifi.data; + + // debug output + std::cout << wifi.ts << ":" << _mes.entries.size() << std::endl; + + // perform vap grouping + const WiFiMeasurements mes = vap->group(_mes); + + + + // error calculation + auto func = [&] (const float* params) -> double { + + // crop z to 1 meter + //params[2] = std::round(params[2]); + + // suggested position + const Point3 pos_m(params[0], params[1], params[2]); + + const float sigma = 8.0; + + double prob = 1.0; + + if (1 == 1) { + + // calculate error for above position using the currently available measurements + for (const WiFiMeasurement& m : mes.entries) { + + // skip non-FHWS APs + if (!LeHelper::isFHWS_AP(m.getAP().getMAC())) {continue;} + + // get model's rssi for the given location + const float rssi_model = wiModel->getRSSI(m.getAP().getMAC(), pos_m); + + // skip APs unknown to the model + if (rssi_model != rssi_model) { + std::cout << "unknown ap: " << m.getAP().getMAC().asString() << std::endl; + continue; + } + + // get scan's rssi + const float rssi_scan = m.getRSSI(); + + // likelyhood + const double p = Distribution::Normal::getProbability(rssi_model, sigma, rssi_scan); + //const double p = Distribution::Region::getProbability(rssi_model, sigma, rssi_scan); + + // adjust + prob *= p; + + } + + } else { + +// //const float limit = -85; + +// for (const AccessPoint& ap : wiModel->getAllAPs()) { + +// // get model's rssi for the given location +// float rssi_model = wiModel->getRSSI(ap.getMAC(), pos_m); +// if (rssi_model < limit) {rssi_model = limit;} + +// // get scan's rssi +// const WiFiMeasurement* mesModel = mes.getForMac(ap.getMAC()); +// float rssi_scan = (mesModel) ? (mesModel->getRSSI()) : (limit); +// if (rssi_scan < limit) {rssi_scan = limit;} + +// // likelyhood +// const double p = Distribution::Normal::getProbability(rssi_model, sigma, rssi_scan); + +// // adjust +// prob *= p; + +// } + + } + + const double err = -prob; + return err; + + }; + + // parameters + float params[3]; + + // USE GENETIC +// std::minstd_rand gen; +// std::uniform_real_distribution distX(mapBBox.getMin().x, mapBBox.getMax().x); +// std::uniform_real_distribution distY(mapBBox.getMin().y, mapBBox.getMax().y); +// std::uniform_real_distribution distZ(mapBBox.getMin().z, mapBBox.getMax().z); +// // initializer for the optimizer: random position within the map's bbox +// auto init = [&] (const int childIdx, float* params) { +// (void) childIdx; +// params[0] = distX(gen); +// params[1] = distY(gen); +// params[2] = distZ(gen); +// }; +// K::NumOptAlgoGenetic opt(3); +// opt.setPopulationSize(400); +// opt.setMaxIterations(20); +// opt.calculateOptimum(func, params, init); + + + // USE RANGE RANDOM WITH COOLING + using LeOpt = K::NumOptAlgoRangeRandom; + const std::vector valRegion = { + LeOpt::MinMax(mapBBox.getMin().x, mapBBox.getMax().x), // x + LeOpt::MinMax(mapBBox.getMin().y, mapBBox.getMax().y), // y + LeOpt::MinMax(mapBBox.getMin().z, mapBBox.getMax().z), // z + }; + + K::NumOptAlgoRangeRandom opt(valRegion); + opt.setPopulationSize(200); + opt.setNumIerations(50); + opt.calculateOptimum(func, params); + + + std::cout << params[0] << "," << params[1] << "," << params[2] << std::endl; + const Point3 curEst(params[0], params[1], params[2]); + path.add(curEst); + + const Timestamp ts = mes.entries.front().getTimestamp(); + + // draw a smoothed version of the path + gpPath->clear(); + for (const Point3 p : path.getAverage(2)) { + const K::GnuplotPoint3 gp3(p.x, p.y, p.z); + gpPath->add(gp3); + } + + static int xxx = 0; ++xxx; + + // groud-truth + const Point3 gt = gtp.get(ts); + plot->gp << "set arrow 1 at " << gt.x << "," << gt.y << "," << gt.z << " to " << gt.x << "," << gt.y << "," << (gt.z+1) << "\n"; + + // error in meter + const float err_m = gt.xy().getDistance(curEst.xy()); // 2D + //const float err_m = gt.getDistance(curEst); // 3D + stats->add(err_m); + mavgMeter.add(err_m); + if (xxx % 3 == 0) { + pet->addErr(ts, mavgMeter.get(), idx); + } + + // error in -log(p) + float gtFloat[3] = {gt.x, gt.y, gt.z}; + const double probOnGT = -func(gtFloat); + const double logProbOnGT = -std::log10(probOnGT); + const double logProbOnGTNorm = (logProbOnGT/mes.entries.size()); + statsProbOnGT->add(logProbOnGTNorm); + pet2->addErr(ts, logProbOnGTNorm, idx); + + if (xxx%4 == 0) { + + plot->plot(); + + pet->plot(); + pef->plot(); + + pet2->plot(); + pef2->plot(); + + } else { + std::this_thread::sleep_for(std::chrono::milliseconds(5)); + } + + + + } + + } + +// // TODO +// void abc(const std::string& fpFile) { + +// // load fingerprints +// calib = new WiFiFingerprints(fpFile); + +// // how to handle VAPs +// VAPGrouper(VAPGrouper::Mode::LAST_MAC_DIGIT_TO_ZERO, VAPGrouper::Aggregation::AVERAGE); + +// // the optimizer +// opt = new WiFiOptimizer::LogDistCeiling(map, *vap, *calib, WiFiOptimizer::LogDistCeiling::Mode::MEDIUM); + +// } + + +// static void dumpWiFiCenterForPath(coconst std::string& fPath) { + +// std::cout << "dump WiFi for " << fPath << std::endl; + +// Offline::FileReader fr(fPath); + +// WiFiModel logDistC + +// for (const auto wifi : fr.wifi) { +// std::cout << wifi.ts << ":" << wifi.data.entries.size() << std::endl; +// } + + +// } + + +}; + +#endif // EVALWIFI_H diff --git a/wifi/EvalWiFiPathMethods.h b/wifi/EvalWiFiPathMethods.h index 447de47..68f3e12 100644 --- a/wifi/EvalWiFiPathMethods.h +++ b/wifi/EvalWiFiPathMethods.h @@ -429,7 +429,7 @@ private: } - double errFuncOther(const float* params, const WiFiMeasurements& mes) { + double errFuncOther(const float* params, const WiFiMeasurements& _mes) { // crop z to 1 meter //params[2] = std::round(params[2]); @@ -439,11 +439,11 @@ private: const float sigma = 8.0; double prob = 1.0; - double error = 0; - int cnt = 0; - //const auto comp = [] (const WiFiMeasurement& m1, const WiFiMeasurement& m2) {return m1.getRSSI() < m2.getRSSI();}; - //const auto& min = std::min_element(mes.entries.begin(), mes.entries.end(), comp); + WiFiMeasurements mes = _mes; + const auto comp = [] (const WiFiMeasurement& m1, const WiFiMeasurement& m2) {return m1.getRSSI() < m2.getRSSI();}; + //std::sort(mes.entries.begin(), mes.entries.end(), comp); + const auto& min = std::min_element(mes.entries.begin(), mes.entries.end(), comp); // calculate error for above position using the currently available measurements for (const WiFiMeasurement& m : mes.entries) { @@ -452,9 +452,13 @@ private: if (!LeHelper::isFHWS_AP(m.getAP().getMAC())) {continue;} // TESTING - //if (m.getAP().getMAC() == min->getAP().getMAC()) {continue;} - //if (m.getRSSI() < -90) {continue;} - //if (m.getRSSI() > -55) {continue;} + + + if (mes.entries.size() > 8) { + //if (m.getRSSI() < mes.entries[1].getRSSI()-10) {continue;} + //if (m.getRSSI() > -65) {continue;} + if (m.getAP().getMAC() == min->getAP().getMAC()) {continue;} + } const float rssi = m.getRSSI(); // const volatile float min = -100; @@ -474,26 +478,7 @@ private: const float rssi_scan = m.getRSSI(); // likelyhood - //double p = Distribution::Normal::getProbability(rssi_model, sigma, rssi_scan); - //const double p = Distribution::Region::getProbability(rssi_model, sigma, rssi_scan); - //const double p = Distribution::Triangle::getProbability(rssi_model, sigma*2, rssi_scan); - //double p = Distribution::Exponential::getProbability(1, std::abs(rssi_model-rssi_scan)); - - double p = Distribution::Exponential::getProbability(1.0, std::abs(rssi_model-rssi_scan)); - - //p = 0.85 * p + 0.15 * (1.0-p); - //p = 0.95 * p + 0.05; - -// const double diff = std::abs(rssi_model - rssi_scan); -//// error += diff; -// ++cnt; -// if (diff < 3) {error += 0.001;} -// else if (diff < 5) {error += 0.001;} -// else if (diff < 8) {error += 0.001;} -// else if (diff < 10) {error += 3.0;} -// else if (diff < 15) {error += 5.0;} -// else {error += 15.0;} -// error += std::pow((diff / 10.0f), 5) / 10.0f; + double p = Distribution::Normal::getProbability(rssi_model, sigma, rssi_scan); // adjust prob *= p; @@ -505,6 +490,53 @@ private: } + + /** USED WITHIN THE PAPER */ + double errFuncOtherExponential(const float* params, const WiFiMeasurements& mes) { + + // crop z to 1 meter + //params[2] = std::round(params[2]); + + // suggested position + const Point3 pos_m(params[0], params[1], params[2]); + + double prob = 1.0; + + //const auto comp = [] (const WiFiMeasurement& m1, const WiFiMeasurement& m2) {return m1.getRSSI() < m2.getRSSI();}; + //const auto& min = std::min_element(mes.entries.begin(), mes.entries.end(), comp); + + // calculate error for above position using the currently available measurements + for (const WiFiMeasurement& m : mes.entries) { + + // skip non-FHWS APs + if (!LeHelper::isFHWS_AP(m.getAP().getMAC())) {continue;} + + // get model's rssi for the given location + const float rssi_model = wiModel->getRSSI(m.getAP().getMAC(), pos_m); + + // skip APs unknown to the model + if (rssi_model != rssi_model) { + std::cout << "unknown ap: " << m.getAP().getMAC().asString() << std::endl; + continue; + } + + // get scan's rssi + const float rssi_scan = m.getRSSI(); + + // likelyhood + double p = Distribution::Exponential::getProbability(1.0, std::abs(rssi_model-rssi_scan)); + + // adjust + prob *= p; + + } + + const double err = -prob; + return err; + + } + + double getVeto(const Point3& pos_m, const WiFiMeasurements& obs) const { struct APR {