double EigenSolverPoissonImageEditing::solve(const NamedParameters& solverParameters, const NamedParameters& problemParameters, bool profileSolve, std::vector<SolverIteration>& iters) { int numUnknowns = 0; std::unordered_map<vec2i, int, vec2iHash> pixelLocationsToIndex; std::vector<vec2i> pixelLocations; size_t pixelCount = m_dims[0] * m_dims[1]; std::vector<float4> h_unknownFloat(pixelCount); std::vector<float4> h_target(pixelCount); std::vector<float> h_mask(pixelCount); findAndCopyArrayToCPU("X", h_unknownFloat, problemParameters); findAndCopyArrayToCPU("T", h_target, problemParameters); findAndCopyArrayToCPU("M", h_mask, problemParameters); for (int y = 0; y < (int)m_dims[1]; ++y) { for (int x = 0; x < (int)m_dims[0]; ++x) { if (h_mask[y*m_dims[0] + x] == 0.0f) { ++numUnknowns; vec2i p(x, y); pixelLocationsToIndex[p] =(int)pixelLocations.size(); pixelLocations.push_back(p); } } } printf("# Unknowns: %d\n", numUnknowns); int numResiduals = (int)pixelLocations.size() * 4; Eigen::VectorXf x_r(numUnknowns), b_r(numResiduals); Eigen::VectorXf x_g(numUnknowns), b_g(numResiduals); Eigen::VectorXf x_b(numUnknowns), b_b(numResiduals); Eigen::VectorXf x_a(numUnknowns), b_a(numResiduals); b_r.setZero(); b_g.setZero(); b_b.setZero(); b_a.setZero(); for (int i = 0; i < pixelLocations.size(); ++i) { vec2i p = pixelLocations[i]; float4 color = sampleImage(h_unknownFloat.data(), p, m_dims[0]); x_r[i] = color.x; //printf("%f\n", color.x); x_g[i] = color.y; x_b[i] = color.z; x_a[i] = color.w; } SpMatrixf A(numResiduals, numUnknowns); A.setZero(); printf("Constructing Matrix\n"); std::vector<Tripf> entriesA; std::vector<vec2i> offsets; offsets.push_back(vec2i(-1, 0)); offsets.push_back(vec2i(1, 0)); offsets.push_back(vec2i(0, -1)); offsets.push_back(vec2i(0, 1)); for (int i = 0; i < pixelLocations.size(); ++i) { vec2i p = pixelLocations[i]; int numInternalNeighbors = 0; float4 g_p = sampleImage(h_target.data(), p, m_dims[0]); int j = 0; for (vec2i off : offsets) { vec2i q = p + off; if (q.x >= 0 && q.y >= 0 && q.x < (int)m_dims[0] && q.y < (int)m_dims[1]) { auto it = pixelLocationsToIndex.find(q); int row = 4 * i + j; if (it == pixelLocationsToIndex.end()) { float4 f_q = sampleImage(h_unknownFloat.data(), q, m_dims[0]); b_r[row] += f_q.x; b_g[row] += f_q.y; b_b[row] += f_q.z; b_a[row] += f_q.w; } else { entriesA.push_back(Tripf(row, it->second, -1.0f)); } entriesA.push_back(Tripf(row, i, 1.0f)); float4 g_q = sampleImage(h_target.data(), q, m_dims[0]); b_r[row] += (g_p.x - g_q.x); b_g[row] += (g_p.y - g_q.y); b_b[row] += (g_p.z - g_q.z); b_a[row] += (g_p.w - g_q.w); } ++j; } } printf("Entries Set\n"); A.setFromTriplets(entriesA.begin(), entriesA.end()); printf("Sparse Matrix Constructed\n"); A.makeCompressed(); printf("Matrix Compressed\n"); { float totalCost = 0.0f; float cost_r = (A*x_r - b_r).squaredNorm(); float cost_g = (A*x_g - b_g).squaredNorm(); float cost_b = (A*x_b - b_b).squaredNorm(); float cost_a = (A*x_a - b_a).squaredNorm(); totalCost = cost_r + cost_g + cost_b + cost_a; printf("Initial Cost: %f : (%f, %f, %f, %f)\n", totalCost, cost_r, cost_g, cost_b, cost_a); } AxEqBSolver solver; solver.setMaxIterations(97); printf("Solvers Initialized\n"); clock_t start = clock(), diff; solver.compute(A); //printf("solver.compute(A)\n"); solveAxEqb(solver, b_r, x_r); //printf("Red solve done\n"); solveAxEqb(solver, b_g, x_g); //printf("Green solve done\n"); solveAxEqb(solver, b_b, x_b); //printf("Blue solve done\n"); solveAxEqb(solver, b_a, x_a); diff = clock() - start; printf("Time taken %f ms\n", diff*1000.0 / double(CLOCKS_PER_SEC)); float totalCost = 0.0f; float cost_r = (A*x_r - b_r).squaredNorm(); float cost_g = (A*x_g - b_g).squaredNorm(); float cost_b = (A*x_b - b_b).squaredNorm(); float cost_a = (A*x_a - b_a).squaredNorm(); totalCost = cost_r + cost_g + cost_b + cost_a; printf("Final Cost: %f : (%f, %f, %f, %f)\n", totalCost, cost_r, cost_g, cost_b, cost_a); for (int i = 0; i < pixelLocations.size(); ++i) { setPixel(h_unknownFloat.data(), pixelLocations[i], m_dims[0], x_r[i], x_g[i], x_b[i]); } findAndCopyToArrayFromCPU("X", h_unknownFloat, problemParameters);; return (double)totalCost; }
bool yee_compare(CompareArgs &args) { if ((args.image_a_->get_width() != args.image_b_->get_width()) or (args.image_a_->get_height() != args.image_b_->get_height())) { args.error_string_ = "Image dimensions do not match\n"; return false; } const auto w = args.image_a_->get_width(); const auto h = args.image_a_->get_height(); const auto dim = w * h; auto identical = true; for (auto i = 0u; i < dim; i++) { if (args.image_a_->get(i) != args.image_b_->get(i)) { identical = false; break; } } if (identical) { args.error_string_ = "Images are binary identical\n"; return true; } // Assuming colorspaces are in Adobe RGB (1998) convert to XYZ. std::vector<float> a_lum(dim); std::vector<float> b_lum(dim); std::vector<float> a_a(dim); std::vector<float> b_a(dim); std::vector<float> a_b(dim); std::vector<float> b_b(dim); if (args.verbose_) { std::cout << "Converting RGB to XYZ\n"; } const auto gamma = args.gamma_; const auto luminance = args.luminance_; #pragma omp parallel for shared(args, a_lum, b_lum, a_a, a_b, b_a, b_b) for (auto y = 0; y < static_cast<ptrdiff_t>(h); y++) { for (auto x = 0u; x < w; x++) { const auto i = x + y * w; const auto a_color_r = powf(args.image_a_->get_red(i) / 255.0f, gamma); const auto a_color_g = powf(args.image_a_->get_green(i) / 255.0f, gamma); const auto a_color_b = powf(args.image_a_->get_blue(i) / 255.0f, gamma); float a_x; float a_y; float a_z; adobe_rgb_to_xyz(a_color_r, a_color_g, a_color_b, a_x, a_y, a_z); float l; xyz_to_lab(a_x, a_y, a_z, l, a_a[i], a_b[i]); const auto b_color_r = powf(args.image_b_->get_red(i) / 255.0f, gamma); const auto b_color_g = powf(args.image_b_->get_green(i) / 255.0f, gamma); const auto b_color_b = powf(args.image_b_->get_blue(i) / 255.0f, gamma); float b_x; float b_y; float b_z; adobe_rgb_to_xyz(b_color_r, b_color_g, b_color_b, b_x, b_y, b_z); xyz_to_lab(b_x, b_y, b_z, l, b_a[i], b_b[i]); a_lum[i] = a_y * luminance; b_lum[i] = b_y * luminance; } } if (args.verbose_) { std::cout << "Constructing Laplacian Pyramids\n"; } const LPyramid la(a_lum, w, h); const LPyramid lb(b_lum, w, h); const auto num_one_degree_pixels = to_degrees(2 * std::tan(args.field_of_view_ * to_radians(.5f))); const auto pixels_per_degree = w / num_one_degree_pixels; if (args.verbose_) { std::cout << "Performing test\n"; } const auto adaptation_level = adaptation(num_one_degree_pixels); float cpd[MAX_PYR_LEVELS]; cpd[0] = 0.5f * pixels_per_degree; for (auto i = 1u; i < MAX_PYR_LEVELS; i++) { cpd[i] = 0.5f * cpd[i - 1]; } const auto csf_max = csf(3.248f, 100.0f); static_assert(MAX_PYR_LEVELS > 2, "MAX_PYR_LEVELS must be greater than 2"); float f_freq[MAX_PYR_LEVELS - 2]; for (auto i = 0u; i < MAX_PYR_LEVELS - 2; i++) { f_freq[i] = csf_max / csf(cpd[i], 100.0f); } auto pixels_failed = 0u; auto error_sum = 0.; #pragma omp parallel for reduction(+ : pixels_failed, error_sum) \ shared(args, a_a, a_b, b_a, b_b, cpd, f_freq) for (auto y = 0; y < static_cast<ptrdiff_t>(h); y++) { for (auto x = 0u; x < w; x++) { const auto index = y * w + x; const auto adapt = std::max((la.get_value(x, y, adaptation_level) + lb.get_value(x, y, adaptation_level)) * 0.5f, 1e-5f); auto sum_contrast = 0.f; auto factor = 0.f; for (auto i = 0u; i < MAX_PYR_LEVELS - 2; i++) { const auto n1 = fabsf(la.get_value(x, y, i) - la.get_value(x, y, i + 1)); const auto n2 = fabsf(lb.get_value(x, y, i) - lb.get_value(x, y, i + 1)); const auto numerator = std::max(n1, n2); const auto d1 = fabsf(la.get_value(x, y, i + 2)); const auto d2 = fabsf(lb.get_value(x, y, i + 2)); const auto denominator = std::max(std::max(d1, d2), 1e-5f); const auto contrast = numerator / denominator; const auto f_mask = mask(contrast * csf(cpd[i], adapt)); factor += contrast * f_freq[i] * f_mask; sum_contrast += contrast; } sum_contrast = std::max(sum_contrast, 1e-5f); factor /= sum_contrast; factor = std::min(std::max(factor, 1.f), 10.f); const auto delta = fabsf(la.get_value(x, y, 0) - lb.get_value(x, y, 0)); error_sum += delta; auto pass = true; // pure luminance test if (delta > factor * tvi(adapt)) { pass = false; } if (not args.luminance_only_) { // CIE delta E test with modifications auto color_scale = args.color_factor_; // ramp down the color test in scotopic regions if (adapt < 10.0f) { // Don't do color test at all. color_scale = 0.0; } const auto da = a_a[index] - b_a[index]; const auto db = a_b[index] - b_b[index]; const auto delta_e = (da * da + db * db) * color_scale; error_sum += delta_e; if (delta_e > factor) { pass = false; } } if (not pass) { pixels_failed++; if (args.image_difference_) { args.image_difference_->set(255, 0, 0, 255, index); } } else { if (args.image_difference_) { args.image_difference_->set(0, 0, 0, 255, index); } } } } const auto error_sum_buff = std::to_string(error_sum) + " error sum\n"; const auto different = std::to_string(pixels_failed) + " pixels are different\n"; // Always output image difference if requested. if (args.image_difference_) { args.image_difference_->write_to_file(args.image_difference_->get_name()); args.error_string_ += "Wrote difference image to "; args.error_string_ += args.image_difference_->get_name(); args.error_string_ += "\n"; } if (pixels_failed < args.threshold_pixels_) { args.error_string_ = "Images are perceptually indistinguishable\n"; args.error_string_ += different; return true; } args.error_string_ = "Images are visibly different\n"; args.error_string_ += different; if (args.sum_errors_) { args.error_string_ += error_sum_buff; } return false; }