mirror of
https://github.com/Dingo-Shenanigans/ReSkate.git
synced 2026-10-27 17:27:08 +01:00
Capture: bound backdrop fitting cost and reject depth outliers
This commit is contained in:
1 parent
0fe1fd0d2d
commit
0d91f6adf9
2 files changed
+68
-21
No files matched your search
@@ -14,8 +14,6 @@
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
namespace dingosdk::capture { bool depth_mask(const PixelView&, Rect, Image&); }
|
||||
|
||||
// Captures without the game (reskate.capture): matting, resizing, the card, the file formats, and a
|
||||
// recording of a turning board or skater found, cut out and saved.
|
||||
// Run with the folder of the menu fonts (External/fonts) as the first argument to test the card too,
|
||||
@@ -452,7 +450,7 @@ void test_depth_cut() {
|
||||
}
|
||||
|
||||
// The cut by the game's depth: a skater standing on a floor that curves away at the sides. Shoes only
|
||||
// 3% nearer than the floor under them are kept (they went missing, 2026-10-24); the curved floor is
|
||||
// 0.5% nearer than the floor under them are kept (they went missing, 2026-10-24); the curved floor is
|
||||
// not taken for the skater.
|
||||
void test_depth_cut_contact() {
|
||||
constexpr std::uint32_t w = 200, h = 150;
|
||||
@@ -482,7 +480,36 @@ void test_depth_cut_contact() {
|
||||
expect(floor == 0, "a backdrop curving away is not taken for the subject");
|
||||
}
|
||||
|
||||
|
||||
// A wide shallow foreground and far depth outliers must not bias the floor fit,
|
||||
// including the floor directly under the subject where sole recovery can grow.
|
||||
void test_depth_cut_wide_and_far() {
|
||||
constexpr std::uint32_t w=800,h=200;
|
||||
for (bool far : {false,true}) {
|
||||
std::vector<float> depth(std::size_t{w}*h);
|
||||
std::vector<std::uint8_t> pixels(std::size_t{w}*h*4,90);
|
||||
for (std::uint32_t y=0;y<h;++y) for (std::uint32_t x=0;x<w;++x) {
|
||||
const double t=(2.0*x+1)/w-1;
|
||||
double d=100-25*t*t;
|
||||
if (x>=300 && x<500 && y>=20 && y<130) d*=.6;
|
||||
if (x>=200 && x<600 && y>=130 && y<140) d*=.97;
|
||||
if (far && x>=650 && x<660) d=9000000;
|
||||
depth[std::size_t{y}*w+x]=static_cast<float>(1/d);
|
||||
}
|
||||
Image picture(w,h);
|
||||
const PixelView frame{pixels.data(),w,h,w*4,true,reinterpret_cast<const std::uint8_t*>(depth.data()),w*4};
|
||||
expect(depth_mask(frame,{0,0,w,h},picture),"wide subject has a depth cut-out");
|
||||
for (std::uint32_t y=130;y<140;++y) for (std::uint32_t x=205;x<595;++x)
|
||||
expect(picture.at(x,y)[3]==255,"wide 3%-nearer shoes survive a curved floor fit");
|
||||
for (std::uint32_t y=140;y<h;++y) for (std::uint32_t x=0;x<w;++x)
|
||||
expect(picture.at(x,y)[3]==0,"floor directly beneath subject stays transparent");
|
||||
for (std::uint32_t y=0;y<h;++y) for (std::uint32_t x=650;x<660;++x)
|
||||
expect(picture.at(x,y)[3]==0,"far depth outliers never become foreground");
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
test_depth_cut_wide_and_far();
|
||||
test_depth_cut_contact();
|
||||
test_depth_cut();
|
||||
test_matte();
|
||||
|
||||
@@ -47,6 +47,7 @@ bool depth_mask(const PixelView& frame, Rect area, Image& picture) {
|
||||
const auto border = std::max<std::uint32_t>(2, w * 6 / 100);
|
||||
std::vector<float> distance(w), edge;
|
||||
std::vector<std::uint8_t> aside(w), near_floor(std::size_t{w} * h);
|
||||
std::vector<int> intervals(std::size_t{w} + 1);
|
||||
float behind = 0;
|
||||
std::array<double, 3> fit{};
|
||||
bool have_fit = false;
|
||||
@@ -62,27 +63,46 @@ bool depth_mask(const PixelView& frame, Rect area, Image& picture) {
|
||||
std::nth_element(edge.begin(), edge.begin() + static_cast<std::ptrdiff_t>(edge.size() / 2), edge.end());
|
||||
behind = edge[edge.size() / 2];
|
||||
}
|
||||
// Set aside what is clearly in front, and a border round it.
|
||||
std::fill(aside.begin(), aside.end(), std::uint8_t{0});
|
||||
// Expand foreground intervals in linear time rather than filling a
|
||||
// wide border separately for every foreground pixel on the render thread.
|
||||
std::fill(intervals.begin(), intervals.end(), 0);
|
||||
for (std::uint32_t x = 0; x < w; ++x)
|
||||
if (distance[x] > 0 && behind > 0 && distance[x] < behind * 0.92f)
|
||||
for (auto k = x > border ? x - border : 0u; k < std::min(w, x + border + 1); ++k) aside[k] = 1;
|
||||
// Least squares a + b t + c t^2 over the rest, t across the row from -1 to 1 (Cramer's rule).
|
||||
double s0 = 0, s1 = 0, s2 = 0, s3 = 0, s4 = 0, r0 = 0, r1 = 0, r2 = 0;
|
||||
if (distance[x] > 0 && behind > 0 && distance[x] < behind * 0.92f) {
|
||||
++intervals[x > border ? x - border : 0u];
|
||||
--intervals[std::min(w, x + border + 1)];
|
||||
}
|
||||
int covered = 0;
|
||||
for (std::uint32_t x = 0; x < w; ++x) {
|
||||
if (aside[x] || distance[x] <= 0) continue;
|
||||
const double t = (2.0 * x + 1) / w - 1, t2 = t * t, v = distance[x];
|
||||
s0 += 1, s1 += t, s2 += t2, s3 += t2 * t, s4 += t2 * t2, r0 += v, r1 += v * t, r2 += v * t2;
|
||||
covered += intervals[x];
|
||||
aside[x] = covered != 0;
|
||||
}
|
||||
const double det = s0 * (s2 * s4 - s3 * s3) - s1 * (s1 * s4 - s3 * s2) + s2 * (s1 * s3 - s2 * s2);
|
||||
if (s0 >= w / 8.0 && std::abs(det) > 1e-9) {
|
||||
fit[0] = (r0 * (s2 * s4 - s3 * s3) - s1 * (r1 * s4 - s3 * r2) + s2 * (r1 * s3 - s2 * r2)) / det;
|
||||
fit[1] = (s0 * (r1 * s4 - r2 * s3) - r0 * (s1 * s4 - s3 * s2) + s2 * (s1 * r2 - r1 * s2)) / det;
|
||||
fit[2] = (s0 * (s2 * r2 - s3 * r1) - s1 * (s1 * r2 - r1 * s2) + r0 * (s1 * s3 - s2 * s2)) / det;
|
||||
have_fit = true;
|
||||
} else if (behind > 0) {
|
||||
fit = {behind, 0, 0}; // too little of the backdrop seen on this row: the outer columns' distance
|
||||
have_fit = true;
|
||||
// Anchor the initial curve to the outer backdrop, so wide shallow shoes
|
||||
// cannot pull it towards themselves. Refine over the row only with
|
||||
// samples agreeing with that anchor. Far props/holes never enter sums.
|
||||
const auto solve = [&](bool edges_only, const std::array<double, 3>& reference) {
|
||||
double s0=0,s1=0,s2=0,s3=0,s4=0,r0=0,r1=0,r2=0;
|
||||
for (std::uint32_t x=0; x<w; ++x) {
|
||||
const double value=distance[x], t=(2.0*x+1)/w-1, t2=t*t;
|
||||
if (aside[x] || value<=0 || behind<=0 || value<behind*.92 || value>behind*1.5) continue;
|
||||
if (edges_only && x>=margin && x<w-margin) continue;
|
||||
const double predicted=reference[0]+reference[1]*t+reference[2]*t2;
|
||||
if (!edges_only && (predicted<=0 || std::abs(value-predicted)>predicted*.015)) continue;
|
||||
s0+=1; s1+=t; s2+=t2; s3+=t2*t; s4+=t2*t2; r0+=value; r1+=value*t; r2+=value*t2;
|
||||
}
|
||||
const double det=s0*(s2*s4-s3*s3)-s1*(s1*s4-s3*s2)+s2*(s1*s3-s2*s2);
|
||||
if (s0<6 || std::abs(det)<=1e-9) return false;
|
||||
const std::array<double,3> result{
|
||||
(r0*(s2*s4-s3*s3)-s1*(r1*s4-s3*r2)+s2*(r1*s3-s2*r2))/det,
|
||||
(s0*(r1*s4-r2*s3)-r0*(s1*s4-s3*s2)+s2*(s1*r2-r1*s2))/det,
|
||||
(s0*(s2*r2-s3*r1)-s1*(s1*r2-r1*s2)+r0*(s1*s3-s2*s2))/det};
|
||||
if (!std::all_of(result.begin(),result.end(),[](double v){return std::isfinite(v);})) return false;
|
||||
fit=result;
|
||||
have_fit=true;
|
||||
return true;
|
||||
};
|
||||
if (behind>0) {
|
||||
fit={behind,0,0}; have_fit=true;
|
||||
if (solve(true,fit)) { const auto anchor=fit; (void)solve(false,anchor); }
|
||||
}
|
||||
for (std::uint32_t x = 0; x < w; ++x) {
|
||||
const double t = (2.0 * x + 1) / w - 1;
|
||||
|
||||
Reference in new issue
Block a user