OpticalFlow.cpp
Go to the documentation of this file.
1 /*
2  * This file is part of ArmarX.
3  *
4  * ArmarX is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License version 2 as
6  * published by the Free Software Foundation.
7  *
8  * ArmarX is distributed in the hope that it will be useful, but
9  * WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11  * GNU General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License
14  * along with this program. If not, see <http://www.gnu.org/licenses/>.
15  *
16  * @package VisionX::ArmarXObjects::OpticalFlow
17  * @author David Sippel ( uddoe at student dot kit dot edu )
18  * @date 2016
19  * @copyright http://www.gnu.org/licenses/gpl-2.0.txt
20  * GNU General Public License
21  */
22 
23 #include "OpticalFlow.h"
24 
25 
27 
28 using namespace armarx;
29 
30 
32 {
35 }
36 
37 
38 
40 {
41  providerName = getProperty<std::string>("providerName").getValue();
42  usingImageProvider(providerName);
43 
44  offeringTopic(getProperty<std::string>("OpticalFlowTopicName").getValue());
45  offeringTopic(getProperty<std::string>("DebugObserverName").getValue());
46 
47  frameRate = getProperty<float>("Framerate").getValue();
48 
49  method = getProperty<OPTICAL_FLOW_METHOD>("Method").getValue();
50 
51  chessboardWidth = getProperty<int>("Chessboard.Width").getValue();
52  chessboardHeight = getProperty<int>("Chessboard.Height").getValue();
53 
54  usingTopic(getProperty<std::string>("CalibrationUpdateTopicName").getValue());
55 }
56 
58 {
59  debugObserver = getTopic<DebugObserverInterfacePrx>(getProperty<std::string>("DebugObserverName").getValue());
60 
61 
62  visionx::ImageProviderInfo imageProviderInfo = getImageProvider(providerName);
63  imageDimension = imageProviderInfo.imageFormat.dimension;
64 
65  imageProviderPrx = getProxy<visionx::ImageProviderInterfacePrx>(providerName);
66 
67  cameraImages = new CByteImage*[2];
68  cameraImages[0] = visionx::tools::createByteImage(imageProviderInfo);
69  cameraImages[1] = visionx::tools::createByteImage(imageProviderInfo);
70 
71 
72 
73  prx = getTopic<OpticalFlowListenerPrx>(getProperty<std::string>("OpticalFlowTopicName").getValue());
74 
75  enableResultImages(1, imageDimension, visionx::ImageType::eRgb);
76 
77 
78  visionx::StereoCalibrationProviderInterfacePrx calibrationProvider = visionx::StereoCalibrationProviderInterfacePrx::checkedCast(imageProviderPrx);
79 
80  if (calibrationProvider)
81  {
82  reportStereoCalibrationChanged(calibrationProvider->getStereoCalibration(), false, "");
83  }
84  else
85  {
86  ARMARX_WARNING << "unable to obtain calibration parameters. using default values";
87 
88  unitFactorX = (1.0 / 640.0) * (59.7);
89  unitFactorY = (1.0 / 480.0) * (44.775);
90  }
91 
92 
93 }
94 
96 {
97 }
98 
100 {
101  /******************
102  * Get gray image
103  ******************/
104 
105  std::unique_lock lock(imageMutex);
106 
107  if (!waitForImages(getProperty<std::string>("providerName").getValue(), 1000))
108  {
109  ARMARX_WARNING << "Timeout while waiting for camera images (>1000ms)";
110  return;
111  }
112 
113  armarx::MetaInfoSizeBasePtr info;
114  int numImages = getImages(getProperty<std::string>("providerName").getValue(), cameraImages, info);
115 
116  if (numImages == 0)
117  {
118  ARMARX_WARNING << "Didn't receive one image! Aborting!";
119  return;
120  }
121 
122  IplImage* ppIplImages[1] = { IplImageAdaptor::Adapt(cameraImages[1]) };
123 
124  // convert to gray
125  cv::Mat grayImage;
126  cv::Mat resultImage = cv::cvarrToMat(ppIplImages[0]);
127  cv::cvtColor(resultImage, grayImage, cv::COLOR_BGR2GRAY);
128 
129  /******************
130  * Compute optical flow
131  ******************/
132 
133  std::vector< cv::Point2f > features;
134 
135  timeDiff = (info->timeProvided - previousTime) / 1000000.0;
136 
137  if ((timeDiff > 1.0) || !previousFeatures.size())
138  {
139  if (timeDiff > 1.0)
140  {
141  ARMARX_INFO << "time delta is larger than 1 sec. discarding previous features.";
142  }
143 
144  if (!previousFeatures.size())
145  {
146  ARMARX_INFO << "no feature found on previous image. discarding previous image.";
147  }
148 
149  resultX = 0.;
150  resultY = 0.;
151  }
152  else
153  {
154  ComputeOpticalFlow(resultImage, grayImage);
155 
156 
157  // ARMARX_LOG << deactivateSpam(5) << "avgDiffX: " << resultX << " avgDiffY: " << resultY << " time " << timeDiff;
158  }
159 
160 
161 
162  prx->reportNewOpticalFlow(resultX, resultY, timeDiff, previousTime);
163 
164  StringVariantBaseMap debugValues;
165  debugValues["x"] = new Variant(resultX);
166  debugValues["y"] = new Variant(resultY);
167  debugValues["delta_t"] = new Variant((timeDiff));
168 
169 
170  debugObserver->setDebugChannel(getName(), debugValues);
171 
172 
173  /******************
174  * Get Next Features
175  ******************/
176 
177  if (method == Chessboard)
178  {
179  cv::Size size = cv::Size(chessboardWidth, chessboardHeight);
180 
181  // + cv::CALIB_CB_FAST_CHECK)
182  bool foundCorners = cv::findChessboardCorners(grayImage, size, features, cv::CALIB_CB_ADAPTIVE_THRESH + cv::CALIB_CB_NORMALIZE_IMAGE);
183 
184  cv::drawChessboardCorners(resultImage, size, features, foundCorners);
185  if (!foundCorners)
186  {
187  ARMARX_WARNING << "Chessboard NOT found!";
188  }
189  }
190 
191  else if (method == Feature)
192  {
193  int maxCorners = 100;
194  double qualityLevel = 0.01;
195  double minDistance = 10.0;
196  cv::Mat mask1;
197  int blockSize = 3;
198  bool useHarrisDetector = true;
199  double k = 0.04;
200 
201  cv::goodFeaturesToTrack(grayImage, features, maxCorners, qualityLevel, minDistance, mask1, blockSize, useHarrisDetector, k);
202  }
203 
204  if (method == Dense)
205  {
206  cv::Mat flow = cv::Mat(grayImage.rows, grayImage.cols, CV_32FC1);
207  double mean_flow = 0.;
208  double rmse_flow = 0.;
209  if (method == Dense)
210  {
211  if (previousImage.size() == grayImage.size())
212  {
213  cv::calcOpticalFlowFarneback(previousImage, grayImage, flow, 0.5, 10, 45, 3, 9, 1.2, 0);
214  flow = (59.7 / 640.) * flow; // pixel to deg
215  flow = (1.0 / timeDiff) * flow; // per frame to per s
216  computeAverageDenseFlow(flow, mean_flow, rmse_flow);
217  }
218  }
219 
220  // drawn optical flow
221  drawDenseFlowMap(flow, resultImage, 10, CV_RGB(0, 255, 0));
222 
223  // send values for plotting
224  StringVariantBaseMap debugValues;
225  debugValues["mean_dense"] = new Variant((mean_flow));
226  debugValues["rmse_dense"] = new Variant((rmse_flow));
227  debugObserver->setDebugChannel(getName(), debugValues);
228  }
229 
230 
231  CByteImage* resultImages[1] = { IplImageAdaptor::Adapt(ppIplImages[0]) };
232  provideResultImages(resultImages);
233 
234  if (method != Dense)
235  {
236  if (!features.size())
237  {
238  ARMARX_WARNING << "no features found";
239  //return;
240  }
241  }
242 
243  previousImage = grayImage;
244  previousFeatures = features;
245  previousTime = info->timeProvided;
246 
247  lock.unlock();
248 
249  if (frameRate > 0.0)
250  {
251  fpsCounter.assureFPS(frameRate);
252  }
253 }
254 
255 
256 void OpticalFlow::ComputeOpticalFlow(cv::Mat resultImage, cv::Mat grayImage)
257 {
258  std::vector<uchar> status;
259  std::vector<float> error;
260 
261  std::vector<cv::Point2f> trackedFeatures;
262  cv::calcOpticalFlowPyrLK(previousImage, grayImage, previousFeatures, trackedFeatures, status, error, cv::Size(21, 21), cv::OPTFLOW_USE_INITIAL_FLOW);
263 
264  std::vector<float> avgDiffx, avgDiffy;
265  for (size_t i = 0; i < trackedFeatures.size(); i++)
266  {
267  if (status[i])
268  {
269  avgDiffx.push_back(previousFeatures[i].x - trackedFeatures[i].x);
270  avgDiffy.push_back(previousFeatures[i].y - trackedFeatures[i].y);
271 
272  cv::circle(resultImage, previousFeatures[i], 2, CV_RGB(255, 0, 0), -1);
273  cv::circle(resultImage, trackedFeatures[i], 2, CV_RGB(0, 0, 255), -1);
274  cv::line(resultImage, previousFeatures[i], trackedFeatures[i], CV_RGB(255, 0, 0), 1);
275  }
276  }
277 
278  if (method == Feature)
279  {
280  //ARMARX_LOG << deactivateSpam(1) << "Removing " << (avgDiffx.size() / 20) << " from x with size: " << avgDiffx.size();
281  //ARMARX_LOG << deactivateSpam(1) << "Removing " << (avgDiffy.size() / 20) << " from y with size: " << avgDiffy.size();
282 
283  for (size_t i = 0; i < (avgDiffx.size() / 20); i++)
284  {
285  avgDiffx = removeHighestAndLowestMember(avgDiffx);
286  }
287  for (size_t i = 0; i < (avgDiffy.size() / 20); i++)
288  {
289  avgDiffy = removeHighestAndLowestMember(avgDiffy);
290  }
291  }
292 
293  if (!avgDiffx.size() || !avgDiffy.size())
294  {
295  ARMARX_WARNING << "No features found in the image, return = optical Flow!";
296  resultX = 0.;
297  resultY = 0.;
298  return;
299  }
300 
301  Eigen::Map<Eigen::ArrayXf> diffX(avgDiffx.data(), avgDiffx.size());
302  Eigen::Map<Eigen::ArrayXf> diffY(avgDiffy.data(), avgDiffy.size());
303 
304  // convert to rad/sec
305  resultX = diffX.mean() / timeDiff;
306  resultY = diffY.mean() / timeDiff;
307 
308  resultX *= unitFactorX;
309  resultY *= unitFactorY;
310 }
311 
312 std::vector<float> OpticalFlow::removeHighestAndLowestMember(std::vector<float>& input)
313 {
314  std::sort(input.begin(), input.end());
315  return std::vector<float>(&input[1], &input[input.size() - 1]);
316 }
317 
318 void OpticalFlow::drawDenseFlowMap(const cv::Mat& flow, cv::Mat& cflowmap, int step, const cv::Scalar& color)
319 {
320 
321  for (int y = 0; y < cflowmap.rows; y += step)
322  for (int x = 0; x < cflowmap.cols; x += step)
323  {
324  const cv::Point2f& fxy = flow.at< cv::Point2f>(y, x);
325  cv::line(cflowmap, cv::Point(x, y), cv::Point(cvRound(x + fxy.x), cvRound(y + fxy.y)), color);
326  cv::circle(cflowmap, cv::Point(cvRound(x + fxy.x), cvRound(y + fxy.y)), 1, color, -1);
327  }
328 }
329 
330 // average optical flow norm on a window of W/2 x H/2 centered in the image
331 void OpticalFlow::computeAverageDenseFlow(const cv::Mat& flow, double& mean, double& rmse)
332 {
333 
334  double dist = 0.;
335  double dist2 = 0.;
336  double d;
337  double count = 0.;
338 
339  for (int y = round(flow.rows / 4); y < round(flow.rows * 3 / 4); y ++)
340  {
341  for (int x = round(flow.cols / 4); x < round(flow.cols * 3 / 4); x ++)
342  {
343  const cv::Point2f& fxy = flow.at< cv::Point2f>(y, x);
344  d = fxy.x * fxy.x + fxy.y * fxy.y;
345  dist += sqrt(d);
346  dist2 += d;
347  count ++;
348  }
349  }
350 
351  rmse = sqrt(dist2 / count);
352  mean = dist / count;
353 }
Feature
Definition: Feature.hpp:44
GfxTL::sqrt
VectorXD< D, T > sqrt(const VectorXD< D, T > &a)
Definition: VectorXD.h:662
armarx::Variant
The Variant class is described here: Variants.
Definition: Variant.h:224
armarx::OpticalFlowPropertyDefinitions
Definition: OpticalFlow.h:60
armarx::StringVariantBaseMap
std::map< std::string, VariantBasePtr > StringVariantBaseMap
Definition: ManagedIceObject.h:111
visionx::ImageProviderInfo::imageFormat
ImageFormatInfo imageFormat
Image format struct that contains all necessary image information.
Definition: ImageProcessor.h:496
armarx::Dense
@ Dense
Definition: OpticalFlow.h:53
visionx::tools::createByteImage
CByteImage * createByteImage(const ImageFormatInfo &imageFormat, const ImageType imageType)
Creates a ByteImage for the destination type specified in the given imageProviderInfo.
visionx::ImageProviderInfo
Definition: ImageProcessor.h:466
OpticalFlow.h
ObserverObjectFactories.h
armarx::status
status
Definition: FiniteStateMachine.h:259
armarx::Chessboard
@ Chessboard
Definition: OpticalFlow.h:53
armarx::mean
std::optional< float > mean(const boost::circular_buffer< NameValueMap > &buffer, const std::string &key)
Definition: KinematicUnitGuiPlugin.cpp:1615
armarx::aron::input
ReaderT::InputType & input
Definition: rw.h:19
armarx::OpticalFlow::process
void process() override
Process the vision component.
Definition: OpticalFlow.cpp:99
visionx::Point
Eigen::Vector3f Point
Definition: ObjectShapeClassification.h:69
armarx::OpticalFlow::createPropertyDefinitions
armarx::PropertyDefinitionsPtr createPropertyDefinitions() override
Definition: OpticalFlow.cpp:31
armarx::OpticalFlow::onConnectImageProcessor
void onConnectImageProcessor() override
Implement this method in the ImageProcessor in order execute parts when the component is fully initia...
Definition: OpticalFlow.cpp:57
armarx::Component::getConfigIdentifier
std::string getConfigIdentifier()
Retrieve config identifier for this component as set in constructor.
Definition: Component.cpp:74
ARMARX_INFO
#define ARMARX_INFO
Definition: Logging.h:174
armarx::OpticalFlow::onExitImageProcessor
void onExitImageProcessor() override
Exit the ImapeProcessor component.
Definition: OpticalFlow.cpp:95
IceUtil::Handle< class PropertyDefinitionContainer >
Scalar
float Scalar
Definition: basic.h:14
ARMARX_WARNING
#define ARMARX_WARNING
Definition: Logging.h:186
armarx::PropertyDefinitionsPtr
IceUtil::Handle< class PropertyDefinitionContainer > PropertyDefinitionsPtr
PropertyDefinitions smart pointer type.
Definition: forward_declarations.h:34
armarx::OpticalFlow::onInitImageProcessor
void onInitImageProcessor() override
Setup the vision component.
Definition: OpticalFlow.cpp:39
armarx
This file offers overloads of toIce() and fromIce() functions for STL container types.
Definition: ArmarXTimeserver.cpp:28