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
28
29#define SMOOTHING_RADIUS 3
30
31using namespace armarx;
32
33void
35{
36 providerName = getProperty<std::string>("providerName").getValue();
37 usingImageProvider(providerName);
38
39 frameRate = getProperty<float>("Framerate").getValue();
40 drawFeatures = getProperty<bool>("drawFeatures").getValue();
41}
42
43void
45{
46 std::unique_lock lock(imageMutex);
47
48 visionx::ImageProviderInfo imageProviderInfo = getImageProvider(providerName);
49 imageProviderPrx = getProxy<visionx::ImageProviderInterfacePrx>(providerName);
50
51 cameraImages = new CByteImage*[2];
52 cameraImages[0] = visionx::tools::createByteImage(imageProviderInfo);
53 cameraImages[1] = visionx::tools::createByteImage(imageProviderInfo);
54
55 //Only one result image (left camera image)
57 1, imageProviderPrx->getImageFormat().dimension, imageProviderPrx->getImageFormat().type);
58}
59
60void
62{
63 std::unique_lock lock(imageMutex);
64 while (oldImages.size() != 0)
65 {
66 oldImages.front().release();
67 oldImages.pop();
68 }
69 deltaTransformations.clear();
70 prevFeatures.clear();
71 prev.release();
72 prevGrey.release();
73 prevTransform.release();
74}
75
76void
78{
79 std::unique_lock lock(imageMutex);
80
81 if (!waitForImages(getProperty<std::string>("providerName").getValue(), 1000))
82 {
83 ARMARX_WARNING << "Timeout while waiting for camera images (>1000ms)";
84 return;
85 }
86
87 armarx::MetaInfoSizeBasePtr info;
88 int numImages =
89 getImages(getProperty<std::string>("providerName").getValue(), cameraImages, info);
90
91 if (numImages == 0)
92 {
93 ARMARX_WARNING << "Didn't receive one image! Aborting!";
94 return;
95 }
96 //Adapt only left camera image
97 IplImage* ppIplImages[1] = {IplImageAdaptor::Adapt(cameraImages[0])};
98
99 //convert to grey image
100 cv::Mat currGrey;
101 cv::Mat curr = cv::cvarrToMat(ppIplImages[0]);
102 cv::cvtColor(curr, currGrey, cv::COLOR_BGR2GRAY);
103
104 cv::Mat tmpImg(curr.size(), curr.type());
105 curr.copyTo(tmpImg);
106
107 //Parameters for goodFeaturesToTrack function
108 int maxCorners = 100;
109 double qualityLevel = 0.01;
110 double minDistance = 5.0;
111 cv::Mat mask;
112 int blockSize = 3;
113 bool useHarrisDetector = true;
114 double k = 0.04;
115 std::vector<cv::Point2f> currFeatures;
116
117 cv::goodFeaturesToTrack(currGrey,
118 currFeatures,
119 maxCorners,
120 qualityLevel,
121 minDistance,
122 mask,
123 blockSize,
124 useHarrisDetector,
125 k);
126
127 //Check if previos image is existing. If not, this is the first image, store it and return
128 if (prev.data == NULL || prevGrey.data == NULL)
129 {
130 ARMARX_LOG << "First image";
131 prev = curr;
132 prevGrey = currGrey;
133 prevFeatures = currFeatures;
134 return;
135 }
136
137 std::vector<uchar> status;
138 std::vector<float> error;
139
140 cv::calcOpticalFlowPyrLK(prevGrey,
141 currGrey,
142 prevFeatures,
143 currFeatures,
144 status,
145 error,
146 cv::Size(21, 21),
147 cv::OPTFLOW_USE_INITIAL_FLOW);
148
149 //Filter only overlapping features for transformation estimation
150 std::vector<cv::Point2f> prevOverlapFeatures, currOverlapFeatures;
151 for (unsigned int i = 0; i < status.size(); i++)
152 {
153 if (status[i] && error[i] < 25.f)
154 {
155 prevOverlapFeatures.push_back(prevFeatures[i]);
156 currOverlapFeatures.push_back(currFeatures[i]);
157 //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));
158 }
159 }
160
161 cv::Mat currTransform =
162 cv::estimateRigidTransform(prevOverlapFeatures, currOverlapFeatures, false);
163
164 //If no transformation is found, use last valid transformation
165 if (currTransform.data == NULL)
166 {
167 return;
168 //prevTransform.copyTo(currTransform);
169 }
170
171 oldImages.push(tmpImg);
172
173 currTransform.copyTo(prevTransform);
174
175 //Get delta x, y and angle from transformation matrix
176 double dx = currTransform.at<double>(0, 2);
177 double dy = currTransform.at<double>(1, 2);
178 double da = atan2(currTransform.at<double>(1, 0), currTransform.at<double>(0, 0));
179
180 deltaTransformations.push_back(cv::Vec3d(dx, dy, da));
181
182 //Check if there are already enough trajectory points to start smoothing
183 if (deltaTransformations.size() <= 2 * SMOOTHING_RADIUS)
184 {
185 while (oldImages.size() > SMOOTHING_RADIUS + 2)
186 {
187 oldImages.pop();
188 }
189 return;
190 }
191
192 assert(oldImages.size() == SMOOTHING_RADIUS + 2);
193
194 //index of currently processed frame
195 int currFrameIndex = deltaTransformations.size() - SMOOTHING_RADIUS - 2;
196
197 cv::Vec3d newTransformation(0, 0, 0);
198
199 for (int i = -SMOOTHING_RADIUS; i <= SMOOTHING_RADIUS; i++)
200 {
201 newTransformation += -deltaTransformations[currFrameIndex + i];
202 }
203
204 newTransformation /= ((2 * SMOOTHING_RADIUS) + 1);
205
206 //create transformation matrix
207 cv::Mat transform(2, 3, CV_64F);
208 transform.at<double>(0, 0) = cos(newTransformation[2]);
209 transform.at<double>(0, 1) = -sin(newTransformation[2]);
210 transform.at<double>(1, 0) = sin(newTransformation[2]);
211 transform.at<double>(1, 1) = cos(newTransformation[2]);
212
213 transform.at<double>(0, 2) = newTransformation[0];
214 transform.at<double>(1, 2) = newTransformation[1];
215
216 cv::Mat currImage = oldImages.front();
217 oldImages.pop();
218 cv::Mat result;
219
220 //apply transformation to image
221 cv::warpAffine(currImage, result, transform, currImage.size());
222
223 if (drawFeatures)
224 {
225 for (const cv::Point2f& p : prevOverlapFeatures)
226 {
227 cv::circle(result, p, 2, CV_RGB(0, 255, 0), -1);
228 }
229 }
230
231
232 CByteImage* output_cimage = new CByteImage();
233 visionx::copyCvMatToIVT(result, output_cimage);
234
235 CByteImage* resultImages[1] = {output_cimage};
236 provideResultImages(std::move(resultImages));
237}
238
245
246void
248{
249 std::unique_lock lock(imageMutex);
250 ARMARX_LOG << "Resetting OpenCV stabilization!";
251 while (oldImages.size() != 0)
252 {
253 oldImages.pop();
254 }
255 deltaTransformations.clear();
256 prevFeatures.clear();
257 prev.data = NULL;
258 prevGrey.data = NULL;
259 prevTransform.data = NULL;
260}
261
262namespace armarx
263{
265} // namespace armarx
#define ARMARX_REGISTER_COMPONENT_EXECUTABLE(ComponentT, applicationName)
Definition Decoupled.h:29
#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)
Brief description of class OpenCVImageStabilizer.
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.