Skip to content

Commit d8d2022

Browse files
author
Fabien Servant
committed
Add remap functions using coordinates maps
1 parent 2b0aee8 commit d8d2022

File tree

3 files changed

+217
-0
lines changed

3 files changed

+217
-0
lines changed

src/aliceVision/image/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ set(image_files_headers
1919
pixelTypes.hpp
2020
Rgb.hpp
2121
Sampler.hpp
22+
remap.hpp
2223
cache.hpp
2324
)
2425

@@ -31,6 +32,7 @@ set(image_files_sources
3132
io.cpp
3233
imageAlgo.cpp
3334
jetColorMap.cpp
35+
remap.cpp
3436
cache.cpp
3537
)
3638

src/aliceVision/image/remap.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// This file is part of the AliceVision project.
2+
// Copyright (c) 2025 AliceVision contributors.
3+
// This Source Code Form is subject to the terms of the Mozilla Public License,
4+
// v. 2.0. If a copy of the MPL was not distributed with this file,
5+
// You can obtain one at https://mozilla.org/MPL/2.0/.
6+
7+
#include <aliceVision/image/remap.hpp>
8+
9+
namespace aliceVision {
10+
namespace image {
11+
12+
13+
14+
} // namespace image
15+
} // namespace aliceVision

src/aliceVision/image/remap.hpp

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
// This file is part of the AliceVision project.
2+
// Copyright (c) 2025 AliceVision contributors.
3+
// This Source Code Form is subject to the terms of the Mozilla Public License,
4+
// v. 2.0. If a copy of the MPL was not distributed with this file,
5+
// You can obtain one at https://mozilla.org/MPL/2.0/.
6+
7+
#pragma once
8+
9+
#include <aliceVision/image/Image.hpp>
10+
#include <aliceVision/image/Sampler.hpp>
11+
#include <aliceVision/system/Logger.hpp>
12+
#include <type_traits>
13+
14+
namespace aliceVision {
15+
namespace image {
16+
17+
/**
18+
* @brief Fill pixels of an image such that dest(i, j) = input(map(i, j).y, map(i, j).x)
19+
* @param[in] src source image (of size H1,W1) to remap
20+
* @param[in] map image (of size H2, W2) containing coordinates in the source image
21+
* @param[in] fillColor the default pixel value if not possible to remap
22+
* @param[out] output image to store the result (will be resized to size H2, W2)
23+
*/
24+
template <typename P>
25+
void remap(const Image<P> & source, const Image<Vec2> & map, const P & fillColor, Image<P> & output)
26+
{
27+
if (output.width() != map.width() || output.height() != map.height())
28+
{
29+
output.resize(map.width(), map.height());
30+
}
31+
32+
const image::Sampler2d<image::SamplerLinear> sampler;
33+
34+
for (int i = 0; i < map.height(); i++)
35+
{
36+
for (int j = 0; j < map.width(); j++)
37+
{
38+
const Vec2 & coord = map(i, j);
39+
40+
const double & x = coord.x();
41+
const double & y = coord.y();
42+
43+
// pick pixel if it is in the image domain
44+
if (source.contains(y, x))
45+
{
46+
output(i, j) = sampler(source, y, x);
47+
}
48+
else
49+
{
50+
output(i, j) = fillColor;
51+
}
52+
}
53+
}
54+
}
55+
56+
/**
57+
* @brief Fill pixels of an image such that dest(i, j) = input(map(i, j).y, map(i, j).x)
58+
* @param[in] src source image (of size H1,W1) to remap
59+
* @param[in] map image (of size H2, W2) containing coordinates in the source image
60+
* @param[in] fillColor the default pixel value if not possible to remap
61+
* @param[out] output image to store the result (will be resized to size H2, W2)
62+
*/
63+
template <typename P>
64+
void remapInter(const Image<P> & source, const Image<Vec2> & map, const P& fillColor, Image<P> & output)
65+
{
66+
if (output.width() != map.width() || output.height() != map.height())
67+
{
68+
output.resize(map.width(), map.height());
69+
}
70+
71+
int srcWidth = source.width();
72+
int srcHeight = source.height();
73+
74+
const image::Sampler2d<image::SamplerLinear> sampler;
75+
76+
for (int i = 0; i < map.height(); i++)
77+
{
78+
for (int j = 0; j < map.width(); j++)
79+
{
80+
const Vec2 & coord = map(i, j);
81+
82+
const double & x = coord.x();
83+
const double & y = coord.y();
84+
85+
double scaleX = 1.0;
86+
if (j < map.width() - 1 && j > 0)
87+
{
88+
scaleX = std::abs(map(i, j + 1).x() - map(i, j - 1).x()) * 0.5;
89+
}
90+
else if (j < map.width() - 1)
91+
{
92+
scaleX = std::abs(map(i, j + 1).x() - x);
93+
}
94+
else if (j > 0)
95+
{
96+
scaleX = std::abs(x - map(i, j - 1).x());
97+
}
98+
99+
double scaleY = 1.0;
100+
if (i < map.height() - 1 && i > 0)
101+
{
102+
scaleY = std::abs(map(i + 1, j).y() - map(i - 1, j).y()) * 0.5;
103+
}
104+
else if (i < map.height() - 1)
105+
{
106+
scaleY = std::abs(map(i + 1, j).y() - y);
107+
}
108+
else if (i > 0)
109+
{
110+
scaleY = std::abs(y - map(i - 1, j).y());
111+
}
112+
113+
const double max_scale = std::max(scaleX, scaleY);
114+
if (scaleX > 1.0 && scaleY > 1.0)
115+
{
116+
//We are doing downsampling here.
117+
//Use area interpolation to avoid aliasing
118+
119+
double halfWidth = scaleX * 0.5;
120+
double halfHeight = scaleY * 0.5;
121+
122+
double x1 = coord.x() - halfWidth;
123+
double x2 = coord.x() + halfWidth;
124+
double y1 = coord.y() - halfHeight;
125+
double y2 = coord.y() + halfHeight;
126+
127+
int ix1 = static_cast<int>(x1);
128+
int ix2 = static_cast<int>(std::ceil(x2));
129+
int iy1 = static_cast<int>(y1);
130+
int iy2 = static_cast<int>(std::ceil(y2));
131+
132+
//Not using sampler as the neigborhood size is dynamic
133+
134+
P sum(0.0);
135+
double wsum = 0.0;
136+
137+
for (int by = iy1; by < iy2; by++)
138+
{
139+
if (by < 0 || by >= srcHeight)
140+
{
141+
continue;
142+
}
143+
144+
//Compute vertical occupancy
145+
// (by + 1) - y1
146+
// or y2 - by
147+
double yocc = std::max(0.0, std::min(double(by + 1), y2) - std::max(double(by), y1));
148+
149+
for (int bx = ix1; bx < ix2; bx++)
150+
{
151+
if (bx < 0 || bx >= srcWidth)
152+
{
153+
continue;
154+
}
155+
156+
//Compute horizontal occupancy
157+
// (bx + 1) - x1
158+
// or x2 - bx
159+
double xocc = std::max(0.0, std::min(double(bx + 1), x2) - std::max(double(bx), x1));
160+
double weight = yocc * xocc;
161+
162+
sum += source(by, bx) * weight;
163+
wsum += weight;
164+
}
165+
}
166+
167+
//Output pixel is the weighted mean of the
168+
//Source pixels in the area covered by the output pixel.
169+
if (wsum > 0.0)
170+
{
171+
sum /= wsum;
172+
173+
if constexpr (std::is_same<P, RGBAfColor>())
174+
{
175+
sum.a() = 1.0;
176+
}
177+
}
178+
179+
output(i, j) = sum;
180+
}
181+
else
182+
{
183+
// Not downsampling, do bilinear interpolation
184+
185+
// pick pixel if it is in the image domain
186+
if (source.contains(y, x))
187+
{
188+
output(i, j) = sampler(source, y, x);
189+
}
190+
else
191+
{
192+
output(i, j) = fillColor;
193+
}
194+
}
195+
}
196+
}
197+
}
198+
199+
} // namespace image
200+
} // namespace aliceVision

0 commit comments

Comments
 (0)