OpenCVImageStabilizer.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::OpenCVImageStabilizer
17 * @author David Sippel ( uddoe at student dot kit dot edu )
18 * @date 2017
19 * @copyright http://www.gnu.org/licenses/gpl-2.0.txt
20 * GNU General Public License
21 */
22
24
26
27#define SMOOTHING_RADIUS 3
28
29using namespace armarx;
30
31void
33{
34 providerName = getProperty<std::string>("providerName").getValue();
35 usingImageProvider(providerName);
36
37 frameRate = getProperty<float>("Framerate").getValue();
38 drawFeatures = getProperty<bool>("drawFeatures").getValue();
39}
40
41void
43{
44 std::unique_lock lock(imageMutex);
45
46 visionx::ImageProviderInfo imageProviderInfo = getImageProvider(providerName);
47 imageProviderPrx = getProxy<visionx::ImageProviderInterfacePrx>(providerName);
48
49 cameraImages = new CByteImage*[2];
50 cameraImages[0] = visionx::tools::createByteImage(imageProviderInfo);
51 cameraImages[1] = visionx::tools::createByteImage(imageProviderInfo);
52
53 //Only one result image (left camera image)
55 1, imageProviderPrx->getImageFormat().dimension, imageProviderPrx->getImageFormat().type);
56}
57
58void
60{
61 std::unique_lock lock(imageMutex);
62 while (oldImages.size() != 0)
63 {
64 oldImages.front().release();
65 oldImages.pop();
66 }
67 deltaTransformations.clear();
68 prevFeatures.clear();
69 prev.release();
70 prevGrey.release();
71 prevTransform.release();
72}
73
74void
76{
77 std::unique_lock lock(imageMutex);
78
79 if (!waitForImages(getProperty<std::string>("providerName").getValue(), 1000))
80 {
81 ARMARX_WARNING << "Timeout while waiting for camera images (>1000ms)";
82 return;
83 }
84
85 armarx::MetaInfoSizeBasePtr info;
86 int numImages =
87 getImages(getProperty<std::string>("providerName").getValue(), cameraImages, info);
88
89 if (numImages == 0)
90 {
91 ARMARX_WARNING << "Didn't receive one image! Aborting!";
92 return;
93 }
94 //Adapt only left camera image
95 IplImage* ppIplImages[1] = {IplImageAdaptor::Adapt(cameraImages[0])};
96
97 //convert to grey image
98 cv::Mat currGrey;
99 cv::Mat curr = cv::cvarrToMat(ppIplImages[0]);
100 cv::cvtColor(curr, currGrey, cv::COLOR_BGR2GRAY);
101
102 cv::Mat tmpImg(curr.size(), curr.type());
103 curr.copyTo(tmpImg);
104
105 //Parameters for goodFeaturesToTrack function
106 int maxCorners = 100;
107 double qualityLevel = 0.01;
108 double minDistance = 5.0;
109 cv::Mat mask;
110 int blockSize = 3;
111 bool useHarrisDetector = true;
112 double k = 0.04;
113 std::vector<cv::Point2f> currFeatures;
114
115 cv::goodFeaturesToTrack(currGrey,
116 currFeatures,
117 maxCorners,
118 qualityLevel,
119 minDistance,
120 mask,
121 blockSize,
122 useHarrisDetector,
123 k);
124
125 //Check if previos image is existing. If not, this is the first image, store it and return
126 if (prev.data == NULL || prevGrey.data == NULL)
127 {
128 ARMARX_LOG << "First image";
129 prev = curr;
130 prevGrey = currGrey;
131 prevFeatures = currFeatures;
132 return;
133 }
134
135 std::vector<uchar> status;
136 std::vector<float> error;
137
138 cv::calcOpticalFlowPyrLK(prevGrey,
139 currGrey,
140 prevFeatures,
141 currFeatures,
142 status,
143 error,
144 cv::Size(21, 21),
145 cv::OPTFLOW_USE_INITIAL_FLOW);
146
147 //Filter only overlapping features for transformation estimation
148 std::vector<cv::Point2f> prevOverlapFeatures, currOverlapFeatures;
149 for (unsigned int i = 0; i < status.size(); i++)
150 {
151 if (status[i] && error[i] < 25.f)
152 {
153 prevOverlapFeatures.push_back(prevFeatures[i]);
154 currOverlapFeatures.push_back(currFeatures[i]);
155 //ARMARX_LOG << deactivateSpam(1) << "error: " << error[i] << " distance: " << std::sqrt(std::pow(prevFeatures[i].x - currFeatures[i].x, 2) + std::pow(prevFeatures[i].y - currFeatures[i].y, 2));
156 }
157 }
158
159 cv::Mat currTransform =
160 cv::estimateRigidTransform(prevOverlapFeatures, currOverlapFeatures, false);
161
162 //If no transformation is found, use last valid transformation
163 if (currTransform.data == NULL)
164 {
165 return;
166 //prevTransform.copyTo(currTransform);
167 }
168
169 oldImages.push(tmpImg);
170
171 currTransform.copyTo(prevTransform);
172
173 //Get delta x, y and angle from transformation matrix
174 double dx = currTransform.at<double>(0, 2);
175 double dy = currTransform.at<double>(1, 2);
176 double da = atan2(currTransform.at<double>(1, 0), currTransform.at<double>(0, 0));
177
178 deltaTransformations.push_back(cv::Vec3d(dx, dy, da));
179
180 //Check if there are already enough trajectory points to start smoothing
181 if (deltaTransformations.size() <= 2 * SMOOTHING_RADIUS)
182 {
183 while (oldImages.size() > SMOOTHING_RADIUS + 2)
184 {
185 oldImages.pop();
186 }
187 return;
188 }
189
190 assert(oldImages.size() == SMOOTHING_RADIUS + 2);
191
192 //index of currently processed frame
193 int currFrameIndex = deltaTransformations.size() - SMOOTHING_RADIUS - 2;
194
195 cv::Vec3d newTransformation(0, 0, 0);
196
197 for (int i = -SMOOTHING_RADIUS; i <= SMOOTHING_RADIUS; i++)
198 {
199 newTransformation += -deltaTransformations[currFrameIndex + i];
200 }
201
202 newTransformation /= ((2 * SMOOTHING_RADIUS) + 1);
203
204 //create transformation matrix
205 cv::Mat transform(2, 3, CV_64F);
206 transform.at<double>(0, 0) = cos(newTransformation[2]);
207 transform.at<double>(0, 1) = -sin(newTransformation[2]);
208 transform.at<double>(1, 0) = sin(newTransformation[2]);
209 transform.at<double>(1, 1) = cos(newTransformation[2]);
210
211 transform.at<double>(0, 2) = newTransformation[0];
212 transform.at<double>(1, 2) = newTransformation[1];
213
214 cv::Mat currImage = oldImages.front();
215 oldImages.pop();
216 cv::Mat result;
217
218 //apply transformation to image
219 cv::warpAffine(currImage, result, transform, currImage.size());
220
221 if (drawFeatures)
222 {
223 for (const cv::Point2f& p : prevOverlapFeatures)
224 {
225 cv::circle(result, p, 2, CV_RGB(0, 255, 0), -1);
226 }
227 }
228
229
230 CByteImage* output_cimage = new CByteImage();
231 visionx::copyCvMatToIVT(result, output_cimage);
232
233 CByteImage* resultImages[1] = {output_cimage};
234 provideResultImages(std::move(resultImages));
235}
236
243
244void
246{
247 std::unique_lock lock(imageMutex);
248 ARMARX_LOG << "Resetting OpenCV stabilization!";
249 while (oldImages.size() != 0)
250 {
251 oldImages.pop();
252 }
253 deltaTransformations.clear();
254 prevFeatures.clear();
255 prev.data = NULL;
256 prevGrey.data = NULL;
257 prevTransform.data = NULL;
258}
#define SMOOTHING_RADIUS
std::string getConfigIdentifier()
Retrieve config identifier for this component as set in constructor.
Definition Component.cpp:90
Property< PropertyType > getProperty(const std::string &name)
Ice::ObjectPrx getProxy(long timeoutMs=0, bool waitForScheduler=true) const
Returns the proxy of this object (optionally it waits for the proxy)
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 resetOpenCVStabilization(const Ice::Current &) override
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
#define ARMARX_WARNING
The logging level for unexpected behaviour, but not a serious problem.
Definition Logging.h:193
#define ARMARX_LOG
Definition Logging.h:165
This file offers overloads of toIce() and fromIce() functions for STL container types.
auto transform(const Container< InputT, Alloc > &in, OutputT(*func)(InputT const &)) -> Container< OutputT, typename std::allocator_traits< Alloc >::template rebind_alloc< OutputT > >
Convenience function (with less typing) to transform a container of type InputT into the same contain...
Definition algorithm.h:351
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.
void copyCvMatToIVT(cv::Mat const &input, CByteImage *output)
Copies the contents of an OpenCV matrix to an IVT CByteImage.