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
27
28using namespace armarx;
29
36
37void
39{
40 providerName = getProperty<std::string>("providerName").getValue();
41 usingImageProvider(providerName);
42
43 offeringTopic(getProperty<std::string>("OpticalFlowTopicName").getValue());
44 offeringTopic(getProperty<std::string>("DebugObserverName").getValue());
45
46 frameRate = getProperty<float>("Framerate").getValue();
47
48 method = getProperty<OPTICAL_FLOW_METHOD>("Method").getValue();
49
50 chessboardWidth = getProperty<int>("Chessboard.Width").getValue();
51 chessboardHeight = getProperty<int>("Chessboard.Height").getValue();
52
53 usingTopic(getProperty<std::string>("CalibrationUpdateTopicName").getValue());
54}
55
56void
58{
60 getProperty<std::string>("DebugObserverName").getValue());
61
62
63 visionx::ImageProviderInfo imageProviderInfo = getImageProvider(providerName);
64 imageDimension = imageProviderInfo.imageFormat.dimension;
65
66 imageProviderPrx = getProxy<visionx::ImageProviderInterfacePrx>(providerName);
67
68 cameraImages = new CByteImage*[2];
69 cameraImages[0] = visionx::tools::createByteImage(imageProviderInfo);
70 cameraImages[1] = visionx::tools::createByteImage(imageProviderInfo);
71
72
74 getProperty<std::string>("OpticalFlowTopicName").getValue());
75
76 enableResultImages(1, imageDimension, visionx::ImageType::eRgb);
77
78
79 visionx::StereoCalibrationProviderInterfacePrx calibrationProvider =
80 visionx::StereoCalibrationProviderInterfacePrx::checkedCast(imageProviderPrx);
81
82 if (calibrationProvider)
83 {
84 reportStereoCalibrationChanged(calibrationProvider->getStereoCalibration(), false, "");
85 }
86 else
87 {
88 ARMARX_WARNING << "unable to obtain calibration parameters. using default values";
89
90 unitFactorX = (1.0 / 640.0) * (59.7);
91 unitFactorY = (1.0 / 480.0) * (44.775);
92 }
93}
94
95void
99
100void
102{
103 /******************
104 * Get gray image
105 ******************/
106
107 std::unique_lock lock(imageMutex);
108
109 if (!waitForImages(getProperty<std::string>("providerName").getValue(), 1000))
110 {
111 ARMARX_WARNING << "Timeout while waiting for camera images (>1000ms)";
112 return;
113 }
114
115 armarx::MetaInfoSizeBasePtr info;
116 int numImages =
117 getImages(getProperty<std::string>("providerName").getValue(), cameraImages, info);
118
119 if (numImages == 0)
120 {
121 ARMARX_WARNING << "Didn't receive one image! Aborting!";
122 return;
123 }
124
125 IplImage* ppIplImages[1] = {IplImageAdaptor::Adapt(cameraImages[1])};
126
127 // convert to gray
128 cv::Mat grayImage;
129 cv::Mat resultImage = cv::cvarrToMat(ppIplImages[0]);
130 cv::cvtColor(resultImage, grayImage, cv::COLOR_BGR2GRAY);
131
132 /******************
133 * Compute optical flow
134 ******************/
135
136 std::vector<cv::Point2f> features;
137
138 timeDiff = (info->timeProvided - previousTime) / 1000000.0;
139
140 if ((timeDiff > 1.0) || !previousFeatures.size())
141 {
142 if (timeDiff > 1.0)
143 {
144 ARMARX_INFO << "time delta is larger than 1 sec. discarding previous features.";
145 }
146
147 if (!previousFeatures.size())
148 {
149 ARMARX_INFO << "no feature found on previous image. discarding previous image.";
150 }
151
152 resultX = 0.;
153 resultY = 0.;
154 }
155 else
156 {
157 ComputeOpticalFlow(resultImage, grayImage);
158
159
160 // ARMARX_LOG << deactivateSpam(5) << "avgDiffX: " << resultX << " avgDiffY: " << resultY << " time " << timeDiff;
161 }
162
163
164 prx->reportNewOpticalFlow(resultX, resultY, timeDiff, previousTime);
165
166 StringVariantBaseMap debugValues;
167 debugValues["x"] = new Variant(resultX);
168 debugValues["y"] = new Variant(resultY);
169 debugValues["delta_t"] = new Variant((timeDiff));
170
171
172 debugObserver->setDebugChannel(getName(), debugValues);
173
174
175 /******************
176 * Get Next Features
177 ******************/
178
179 if (method == Chessboard)
180 {
181 cv::Size size = cv::Size(chessboardWidth, chessboardHeight);
182
183 // + cv::CALIB_CB_FAST_CHECK)
184 bool foundCorners = cv::findChessboardCorners(
185 grayImage, size, features, cv::CALIB_CB_ADAPTIVE_THRESH + cv::CALIB_CB_NORMALIZE_IMAGE);
186
187 cv::drawChessboardCorners(resultImage, size, features, foundCorners);
188 if (!foundCorners)
189 {
190 ARMARX_WARNING << "Chessboard NOT found!";
191 }
192 }
193
194 else if (method == Feature)
195 {
196 int maxCorners = 100;
197 double qualityLevel = 0.01;
198 double minDistance = 10.0;
199 cv::Mat mask1;
200 int blockSize = 3;
201 bool useHarrisDetector = true;
202 double k = 0.04;
203
204 cv::goodFeaturesToTrack(grayImage,
205 features,
206 maxCorners,
207 qualityLevel,
208 minDistance,
209 mask1,
210 blockSize,
211 useHarrisDetector,
212 k);
213 }
214
215 if (method == Dense)
216 {
217 cv::Mat flow = cv::Mat(grayImage.rows, grayImage.cols, CV_32FC1);
218 double mean_flow = 0.;
219 double rmse_flow = 0.;
220 if (method == Dense)
221 {
222 if (previousImage.size() == grayImage.size())
223 {
224 cv::calcOpticalFlowFarneback(
225 previousImage, grayImage, flow, 0.5, 10, 45, 3, 9, 1.2, 0);
226 flow = (59.7 / 640.) * flow; // pixel to deg
227 flow = (1.0 / timeDiff) * flow; // per frame to per s
228 computeAverageDenseFlow(flow, mean_flow, rmse_flow);
229 }
230 }
231
232 // drawn optical flow
233 drawDenseFlowMap(flow, resultImage, 10, CV_RGB(0, 255, 0));
234
235 // send values for plotting
236 StringVariantBaseMap debugValues;
237 debugValues["mean_dense"] = new Variant((mean_flow));
238 debugValues["rmse_dense"] = new Variant((rmse_flow));
239 debugObserver->setDebugChannel(getName(), debugValues);
240 }
241
242
243 CByteImage* resultImages[1] = {IplImageAdaptor::Adapt(ppIplImages[0])};
244 provideResultImages(resultImages);
245
246 if (method != Dense)
247 {
248 if (!features.size())
249 {
250 ARMARX_WARNING << "no features found";
251 //return;
252 }
253 }
254
255 previousImage = grayImage;
256 previousFeatures = features;
257 previousTime = info->timeProvided;
258
259 lock.unlock();
260
261 if (frameRate > 0.0)
262 {
263 fpsCounter.assureFPS(frameRate);
264 }
265}
266
267void
268OpticalFlow::ComputeOpticalFlow(cv::Mat resultImage, cv::Mat grayImage)
269{
270 std::vector<uchar> status;
271 std::vector<float> error;
272
273 std::vector<cv::Point2f> trackedFeatures;
274 cv::calcOpticalFlowPyrLK(previousImage,
275 grayImage,
276 previousFeatures,
277 trackedFeatures,
278 status,
279 error,
280 cv::Size(21, 21),
281 cv::OPTFLOW_USE_INITIAL_FLOW);
282
283 std::vector<float> avgDiffx, avgDiffy;
284 for (size_t i = 0; i < trackedFeatures.size(); i++)
285 {
286 if (status[i])
287 {
288 avgDiffx.push_back(previousFeatures[i].x - trackedFeatures[i].x);
289 avgDiffy.push_back(previousFeatures[i].y - trackedFeatures[i].y);
290
291 cv::circle(resultImage, previousFeatures[i], 2, CV_RGB(255, 0, 0), -1);
292 cv::circle(resultImage, trackedFeatures[i], 2, CV_RGB(0, 0, 255), -1);
293 cv::line(resultImage, previousFeatures[i], trackedFeatures[i], CV_RGB(255, 0, 0), 1);
294 }
295 }
296
297 if (method == Feature)
298 {
299 //ARMARX_LOG << deactivateSpam(1) << "Removing " << (avgDiffx.size() / 20) << " from x with size: " << avgDiffx.size();
300 //ARMARX_LOG << deactivateSpam(1) << "Removing " << (avgDiffy.size() / 20) << " from y with size: " << avgDiffy.size();
301
302 for (size_t i = 0; i < (avgDiffx.size() / 20); i++)
303 {
304 avgDiffx = removeHighestAndLowestMember(avgDiffx);
305 }
306 for (size_t i = 0; i < (avgDiffy.size() / 20); i++)
307 {
308 avgDiffy = removeHighestAndLowestMember(avgDiffy);
309 }
310 }
311
312 if (!avgDiffx.size() || !avgDiffy.size())
313 {
314 ARMARX_WARNING << "No features found in the image, return = optical Flow!";
315 resultX = 0.;
316 resultY = 0.;
317 return;
318 }
319
320 Eigen::Map<Eigen::ArrayXf> diffX(avgDiffx.data(), avgDiffx.size());
321 Eigen::Map<Eigen::ArrayXf> diffY(avgDiffy.data(), avgDiffy.size());
322
323 // convert to rad/sec
324 resultX = diffX.mean() / timeDiff;
325 resultY = diffY.mean() / timeDiff;
326
327 resultX *= unitFactorX;
328 resultY *= unitFactorY;
329}
330
331std::vector<float>
332OpticalFlow::removeHighestAndLowestMember(std::vector<float>& input)
333{
334 std::sort(input.begin(), input.end());
335 return std::vector<float>(&input[1], &input[input.size() - 1]);
336}
337
338void
339OpticalFlow::drawDenseFlowMap(const cv::Mat& flow,
340 cv::Mat& cflowmap,
341 int step,
342 const cv::Scalar& color)
343{
344
345 for (int y = 0; y < cflowmap.rows; y += step)
346 for (int x = 0; x < cflowmap.cols; x += step)
347 {
348 const cv::Point2f& fxy = flow.at<cv::Point2f>(y, x);
349 cv::line(cflowmap,
350 cv::Point(x, y),
351 cv::Point(cvRound(x + fxy.x), cvRound(y + fxy.y)),
352 color);
353 cv::circle(cflowmap, cv::Point(cvRound(x + fxy.x), cvRound(y + fxy.y)), 1, color, -1);
354 }
355}
356
357// average optical flow norm on a window of W/2 x H/2 centered in the image
358void
359OpticalFlow::computeAverageDenseFlow(const cv::Mat& flow, double& mean, double& rmse)
360{
361
362 double dist = 0.;
363 double dist2 = 0.;
364 double d;
365 double count = 0.;
366
367 for (int y = round(flow.rows / 4); y < round(flow.rows * 3 / 4); y++)
368 {
369 for (int x = round(flow.cols / 4); x < round(flow.cols * 3 / 4); x++)
370 {
371 const cv::Point2f& fxy = flow.at<cv::Point2f>(y, x);
372 d = fxy.x * fxy.x + fxy.y * fxy.y;
373 dist += sqrt(d);
374 dist2 += d;
375 count++;
376 }
377 }
378
379 rmse = sqrt(dist2 / count);
380 mean = dist / count;
381}
382
383namespace armarx
384{
386} // namespace armarx
#define ARMARX_REGISTER_COMPONENT_EXECUTABLE(ComponentT, applicationName)
Definition Decoupled.h:29
std::string getConfigIdentifier()
Retrieve config identifier for this component as set in constructor.
Definition Component.cpp:90
Property< PropertyType > getProperty(const std::string &name)
void offeringTopic(const std::string &name)
Registers a topic for retrival after initialization.
TopicProxyType getTopic(const std::string &name)
Returns a proxy of the specified topic.
void usingTopic(const std::string &name, bool orderedPublishing=false)
Registers a proxy for subscription after initialization.
std::string getName() const
Retrieve name of object.
Ice::ObjectPrx getProxy(long timeoutMs=0, bool waitForScheduler=true) const
Returns the proxy of this object (optionally it waits for the proxy)
Brief description of class OpticalFlow.
void onConnectImageProcessor() override
Implement this method in the ImageProcessor in order execute parts when the component is fully initia...
void onExitImageProcessor() override
Exit the ImapeProcessor component.
armarx::PropertyDefinitionsPtr createPropertyDefinitions() override
void process() override
Process the vision component.
void onInitImageProcessor() override
Setup the vision component.
void reportStereoCalibrationChanged(const visionx::StereoCalibration &stereoCalibration, bool, const std::string &, const Ice::Current &c=Ice::emptyCurrent) override
static std::string GetDefaultName()
The Variant class is described here: Variants.
Definition Variant.h:224
void enableResultImages(int numberImages, ImageDimension imageDimension, ImageType imageType, const std::string &name="")
Enables visualization.
void usingImageProvider(std::string name)
Registers a delayed topic subscription and a delayed provider proxy retrieval which all will be avail...
bool waitForImages(int milliseconds=1000)
Wait for new images.
ImageProviderInfo getImageProvider(std::string name, ImageType destinationImageType=eRgb, bool waitForProxy=false)
Select an ImageProvider.
int getImages(CByteImage **ppImages)
Poll images from provider.
void provideResultImages(CByteImage **images, armarx::MetaInfoSizeBasePtr info=nullptr)
sends result images for visualization
ImageFormatInfo imageFormat
Image format struct that contains all necessary image information.
#define ARMARX_INFO
The normal logging level.
Definition Logging.h:181
#define ARMARX_WARNING
The logging level for unexpected behaviour, but not a serious problem.
Definition Logging.h:193
VectorXD< D, T > sqrt(const VectorXD< D, T > &a)
Definition VectorXD.h:704
This file offers overloads of toIce() and fromIce() functions for STL container types.
std::optional< float > mean(const boost::circular_buffer< NameValueMap > &buffer, const std::string &key)
std::map< std::string, VariantBasePtr > StringVariantBaseMap
@ Chessboard
Definition OpticalFlow.h:50
IceUtil::Handle< class PropertyDefinitionContainer > PropertyDefinitionsPtr
PropertyDefinitions smart pointer type.
CByteImage * createByteImage(const ImageFormatInfo &imageFormat, const ImageType imageType)
Creates a ByteImage for the destination type specified in the given imageProviderInfo.