feat: add ADetailer support (#1785)

This commit is contained in:
leejet
2026-07-14 23:59:11 +08:00
committed by GitHub
parent c00a9e956d
commit a8a91b24cd
19 changed files with 1874 additions and 14 deletions

View File

@@ -6,6 +6,9 @@ For detailed command-line arguments, run:
./bin/sd-cli -h
```
For direct image repair or automatic post-generation YOLOv8 detection followed by cropped inpainting, see
[ADetailer](../../docs/adetailer.md).
Metadata mode inspects PNG/JPEG container metadata without loading any model:
```bash

View File

@@ -199,7 +199,7 @@ struct SDCliParams {
options.manual_options = {
{"-M",
"--mode",
"run mode, one of [img_gen, vid_gen, upscale, convert, metadata], default: img_gen",
"run mode, one of [img_gen, adetailer, vid_gen, upscale, convert, metadata], default: img_gen",
on_mode_arg},
{"",
"--preview",
@@ -566,6 +566,65 @@ bool save_results(const SDCliParams& cli_params,
return sucessful_reults != 0;
}
static bool apply_adetailer(sd_ctx_t* sd_ctx,
const sd_ctx_params_t& sd_ctx_params,
const SDContextParams& ctx_params,
const SDGenerationParams& gen_params,
const sd_img_gen_params_t& img_gen_params,
SDMode mode,
SDImageVec& results,
int num_results) {
if (gen_params.ad_model_path.empty()) {
return true;
}
sd_adetailer_params_t ad_params{};
ad_params.prompt = gen_params.ad_prompt.empty() ? nullptr : gen_params.ad_prompt.c_str();
ad_params.negative_prompt = gen_params.ad_negative_prompt.empty() ? nullptr : gen_params.ad_negative_prompt.c_str();
ad_params.extra_ad_args = gen_params.extra_ad_args.c_str();
ADetailerCtxPtr ad_ctx(new_adetailer_ctx(gen_params.ad_model_path.c_str(),
ctx_params.n_threads,
sd_ctx_params.backend,
sd_ctx_params.params_backend));
if (ad_ctx == nullptr) {
LOG_ERROR("new_adetailer_ctx failed");
return false;
}
for (int i = 0; i < num_results; ++i) {
if (results[i].data == nullptr) {
continue;
}
sd_img_gen_params_t ad_generation_params = img_gen_params;
ad_generation_params.seed = img_gen_params.seed + i;
if (mode == IMG_GEN) {
ad_generation_params.width = 512;
ad_generation_params.height = 512;
ad_generation_params.strength = 0.4f;
}
sd_image_t* detailed_images = nullptr;
int detailed_count = 0;
if (!adetail_image(ad_ctx.get(),
sd_ctx,
results[i],
&ad_params,
&ad_generation_params,
&detailed_images,
&detailed_count) ||
detailed_count <= 0 || detailed_images == nullptr || detailed_images[0].data == nullptr) {
free_sd_images(detailed_images, detailed_count);
LOG_ERROR("ADetailer failed for image %d", i + 1);
return false;
}
free(results[i].data);
results[i] = detailed_images[0];
detailed_images[0] = {0, 0, 0, nullptr};
free_sd_images(detailed_images, detailed_count);
}
return true;
}
int main(int argc, const char* argv[]) {
if (argc > 1 && std::string(argv[1]) == "--version") {
std::cout << version_string() << "\n";
@@ -598,6 +657,11 @@ int main(int argc, const char* argv[]) {
return 0;
}
if (!gen_params.ad_model_path.empty() && cli_params.mode != IMG_GEN && cli_params.mode != ADETAILER) {
LOG_ERROR("--ad-model is only supported in image generation and adetailer modes");
return 1;
}
if (gen_params.video_frames > 4) {
size_t last_dot_pos = cli_params.preview_path.find_last_of(".");
std::string base_path = cli_params.preview_path;
@@ -806,15 +870,22 @@ int main(int argc, const char* argv[]) {
gen_params.sample_params.scheduler = sd_get_default_scheduler(sd_ctx.get(), gen_params.sample_params.sample_method);
}
if (cli_params.mode == IMG_GEN) {
sd_img_gen_params_t img_gen_params = gen_params.to_sd_img_gen_params_t();
sd_img_gen_params_t img_gen_params{};
const bool use_img_gen_params = cli_params.mode == IMG_GEN || cli_params.mode == ADETAILER;
if (use_img_gen_params) {
img_gen_params = gen_params.to_sd_img_gen_params_t();
}
if (cli_params.mode == IMG_GEN) {
sd_image_t* generated_images = nullptr;
if (!generate_image(sd_ctx.get(), &img_gen_params, &generated_images, &num_results)) {
generated_images = nullptr;
num_results = 0;
}
results.adopt(generated_images, num_results);
} else if (cli_params.mode == ADETAILER) {
num_results = 1;
results.push_back(gen_params.init_image.release());
} else if (cli_params.mode == VID_GEN) {
sd_vid_gen_params_t vid_gen_params = gen_params.to_sd_vid_gen_params_t();
sd_image_t* generated_video = nullptr;
@@ -828,6 +899,18 @@ int main(int argc, const char* argv[]) {
LOG_ERROR("generate failed");
return 1;
}
if (use_img_gen_params &&
!apply_adetailer(sd_ctx.get(),
sd_ctx_params,
ctx_params,
gen_params,
img_gen_params,
cli_params.mode,
results,
num_results)) {
return 1;
}
}
int upscale_factor = 4; // unused for RealESRGAN_x4plus_anime_6B.pth

View File

@@ -30,6 +30,7 @@ namespace fs = std::filesystem;
const char* const modes_str[] = {
"img_gen",
"adetailer",
"vid_gen",
"convert",
"upscale",
@@ -922,6 +923,26 @@ ArgOptions SDGenerationParams::get_options() {
"the negative prompt (default: \"\")",
0,
&negative_prompt},
{"",
"--ad-model",
"path to a converted YOLOv8 detection model for ADetailer",
0,
&ad_model_path},
{"",
"--ad-prompt",
"ADetailer prompt; empty inherits the main prompt, supports [PROMPT], [SEP], and [SKIP]",
0,
&ad_prompt},
{"",
"--ad-negative-prompt",
"ADetailer negative prompt; empty inherits the main negative prompt, supports [PROMPT] and [SEP]",
0,
&ad_negative_prompt},
{"",
"--extra-ad-args",
"extra ADetailer args, key=value list. Supports input_size, confidence, nms, max_detections, mask_k_largest, mask_min_ratio, mask_max_ratio, dilate_erode, x_offset, y_offset, mask_mode, merge_masks, invert_mask, mask_blur, inpaint_padding, inpaint_width, inpaint_height, denoising_strength, steps, cfg_scale, sample_method, scheduler, sort_by",
(int)',',
&extra_ad_args},
{"-i",
"--init-img",
"path to the init image",
@@ -1842,6 +1863,10 @@ bool SDGenerationParams::from_json_str(
load_if_exists("prompt", prompt);
load_if_exists("negative_prompt", negative_prompt);
load_if_exists("ad_model", ad_model_path);
load_if_exists("ad_prompt", ad_prompt);
load_if_exists("ad_negative_prompt", ad_negative_prompt);
load_if_exists("extra_ad_args", extra_ad_args);
load_if_exists("cache_mode", cache_mode);
load_if_exists("cache_option", cache_option);
load_if_exists("scm_mask", scm_mask);
@@ -2358,13 +2383,19 @@ bool SDGenerationParams::validate(SDMode mode) {
}
}
if (mode == UPSCALE) {
if (mode == UPSCALE || mode == ADETAILER) {
if (init_image_path.length() == 0) {
LOG_ERROR("error: upscale mode needs an init image (--init-img)\n");
LOG_ERROR("error: %s mode needs an init image (--init-img)\n",
mode == UPSCALE ? "upscale" : "adetailer");
return false;
}
}
if (mode == ADETAILER && ad_model_path.empty()) {
LOG_ERROR("error: adetailer mode needs a detector model (--ad-model)\n");
return false;
}
return true;
}
@@ -2587,6 +2618,10 @@ std::string SDGenerationParams::to_string() const {
<< " high_noise_loras: \"" << high_noise_loras_str << "\",\n"
<< " prompt: \"" << prompt << "\",\n"
<< " negative_prompt: \"" << negative_prompt << "\",\n"
<< " ad_model_path: \"" << ad_model_path << "\",\n"
<< " ad_prompt: \"" << ad_prompt << "\",\n"
<< " ad_negative_prompt: \"" << ad_negative_prompt << "\",\n"
<< " extra_ad_args: \"" << extra_ad_args << "\",\n"
<< " clip_skip: " << clip_skip << ",\n"
<< " width: " << width << ",\n"
<< " height: " << height << ",\n"
@@ -2701,8 +2736,13 @@ std::string build_sdcpp_image_metadata_json(const SDContextParams& ctx_params,
int64_t seed,
SDMode mode) {
json root;
root["schema"] = "sdcpp.image.params/v1";
root["mode"] = mode == VID_GEN ? "vid_gen" : "img_gen";
root["schema"] = "sdcpp.image.params/v1";
root["mode"] = "img_gen";
if (mode == VID_GEN) {
root["mode"] = "vid_gen";
} else if (mode == ADETAILER) {
root["mode"] = "adetailer";
}
root["generator"] = {
{"name", "stable-diffusion.cpp"},
{"version", safe_json_string(sd_version())},
@@ -2716,6 +2756,14 @@ std::string build_sdcpp_image_metadata_json(const SDContextParams& ctx_params,
{"positive", gen_params.prompt},
{"negative", gen_params.negative_prompt},
};
if (!gen_params.ad_model_path.empty()) {
root["adetailer"] = {
{"model", sd_basename(gen_params.ad_model_path)},
{"prompt", gen_params.ad_prompt},
{"negative_prompt", gen_params.ad_negative_prompt},
{"extra_args", gen_params.extra_ad_args},
};
}
root["sampling"] = build_sampling_metadata_json(gen_params.sample_params,
gen_params.skip_layers,
&gen_params.custom_sigmas);
@@ -2870,6 +2918,18 @@ std::string get_image_params(const SDContextParams& ctx_params,
if (!gen_params.extra_sample_args.empty()) {
parameter_string += "Extra sample args: " + gen_params.extra_sample_args + ", ";
}
if (!gen_params.ad_model_path.empty()) {
parameter_string += "ADetailer model: " + sd_basename(gen_params.ad_model_path) + ", ";
if (!gen_params.ad_prompt.empty()) {
parameter_string += "ADetailer prompt: " + gen_params.ad_prompt + ", ";
}
if (!gen_params.ad_negative_prompt.empty()) {
parameter_string += "ADetailer negative prompt: " + gen_params.ad_negative_prompt + ", ";
}
if (!gen_params.extra_ad_args.empty()) {
parameter_string += "ADetailer args: " + gen_params.extra_ad_args + ", ";
}
}
parameter_string += "Seed: " + std::to_string(seed) + ", ";
parameter_string += "Size: " + std::to_string(gen_params.get_resolved_width()) + "x" + std::to_string(gen_params.get_resolved_height()) + ", ";
parameter_string += "Model: " + sd_basename(ctx_params.model_path) + ", ";

View File

@@ -16,10 +16,11 @@
#define BOOL_STR(b) ((b) ? "true" : "false")
extern const char* const modes_str[];
#define SD_ALL_MODES_STR "img_gen, vid_gen, convert, upscale, metadata"
#define SD_ALL_MODES_STR "img_gen, adetailer, vid_gen, convert, upscale, metadata"
enum SDMode {
IMG_GEN,
ADETAILER,
VID_GEN,
CONVERT,
UPSCALE,
@@ -187,6 +188,10 @@ struct SDGenerationParams {
// User-facing input fields.
std::string prompt;
std::string negative_prompt;
std::string ad_model_path;
std::string ad_prompt;
std::string ad_negative_prompt;
std::string extra_ad_args;
int clip_skip = -1; // <= 0 represents unspecified
int width = -1;
int height = -1;

View File

@@ -40,12 +40,21 @@ struct UpscalerCtxDeleter {
}
};
struct ADetailerCtxDeleter {
void operator()(adetailer_ctx_t* ctx) const {
if (ctx != nullptr) {
free_adetailer_ctx(ctx);
}
}
};
template <typename T>
using FreeUniquePtr = std::unique_ptr<T, FreeDeleter>;
using FilePtr = std::unique_ptr<FILE, FileCloser>;
using SDCtxPtr = std::unique_ptr<sd_ctx_t, SDCtxDeleter>;
using UpscalerCtxPtr = std::unique_ptr<upscaler_ctx_t, UpscalerCtxDeleter>;
using FilePtr = std::unique_ptr<FILE, FileCloser>;
using SDCtxPtr = std::unique_ptr<sd_ctx_t, SDCtxDeleter>;
using UpscalerCtxPtr = std::unique_ptr<upscaler_ctx_t, UpscalerCtxDeleter>;
using ADetailerCtxPtr = std::unique_ptr<adetailer_ctx_t, ADetailerCtxDeleter>;
class SDImageOwner {
private: