<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>HanaTaka2137&apos;s Blog</title><description>hanaTaka2137(XiYang6666) 的个人博客</description><link>https://blog.xiyang6666.top/</link><language>zh_CN</language><item><title>训练 Diffusion LoRA 模型</title><link>https://blog.xiyang6666.top/posts/tinkering/2026-2-13_%E8%AE%AD%E7%BB%83-diffusion-lora-%E6%A8%A1%E5%9E%8B/</link><guid isPermaLink="true">https://blog.xiyang6666.top/posts/tinkering/2026-2-13_%E8%AE%AD%E7%BB%83-diffusion-lora-%E6%A8%A1%E5%9E%8B/</guid><description>数据来源: 番剧截图 &amp; Gelbooru 算力平台: AutoDL</description><pubDate>Fri, 13 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;总览&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;数据来源: 番剧截图 &amp;amp; &lt;a href=&quot;https://gelbooru.com/&quot;&gt;Gelbooru&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;算力平台: &lt;a href=&quot;https://www.autodl.com&quot;&gt;AutoDL&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;准备工作&lt;/h2&gt;
&lt;p&gt;为了训练一个 Diffusion LoRA 模型, 一定量的图片素材做数据集必不可少.&lt;/p&gt;
&lt;p&gt;我这里以白咲花为例, 图片素材来自番剧截图以及用爬虫从 &lt;a href=&quot;https://gelbooru.com/&quot;&gt;Gelbooru&lt;/a&gt; 爬取的图片, 总计 1497 张.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://gelbooru.com/&quot;&gt;Gelbooru&lt;/a&gt; 对爬虫比较友好, 注册一个账号即可以 json 格式批量爬取图片, 甚至可以获取图片标签方便训练.&lt;/p&gt;
&lt;p&gt;参考脚本:&lt;/p&gt;
&lt;p&gt;&amp;lt;details&amp;gt;
&amp;lt;summary&amp;gt;展开查看参考脚本&amp;lt;/summary&amp;gt;&lt;/p&gt;
&lt;p&gt;需要安装的库: &lt;code&gt;rich&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import asyncio
import functools
import itertools
from urllib.parse import urlparse

from httpx import AsyncClient
from rich.progress import Progress
from pathlib import Path


API_KEY = &quot;YOUR API KEY&quot;
UID = &quot;YOUR UID&quot;
TAGS = &quot;shirosaki_hana&quot;
SEMAPHORE = asyncio.Semaphore(100)


def async_retry(delay: float = 1.0, backoff: float = 2.0, exceptions=(Exception,)):
    def decorator(func):
        @functools.wraps(func)
        async def wrapper(*args, **kwargs):
            wait = delay
            attempt = 0
            while True:
                try:
                    return await func(*args, **kwargs)
                except exceptions as e:
                    attempt += 1
                    print(
                        f&quot;[Retry {attempt}] {func.__name__}({&apos;, &apos;.join(itertools.chain(map(str, args), [f&apos;{k}={v}&apos; for k, v in kwargs.items()]))}) failed: {e}, retrying in {wait:.1f}s...&quot;
                    )
                    await asyncio.sleep(wait)
                    wait *= backoff  # 退避

        return wrapper

    return decorator


def with_semaphore(sem: asyncio.Semaphore):
    def decorator(func):
        @functools.wraps(func)
        async def wrapper(*args, **kwargs):
            async with sem:
                return await func(*args, **kwargs)

        return wrapper

    return decorator


def get_url_suffix(url: str):
    path = urlparse(url).path
    return Path(path).suffix


async def main():
    progress = Progress()
    async with AsyncClient(trust_env=True) as client:
        # 第一页
        response = await client.get(
            f&quot;https://gelbooru.com/index.php?page=dapi&amp;amp;s=post&amp;amp;q=index&amp;amp;json=1&amp;amp;tags={TAGS}&amp;amp;api_key={API_KEY}&amp;amp;user_id={UID}&quot;
        )
        page_count = response.json()[&quot;@attributes&quot;][&quot;count&quot;]

        progress.start()
        task = progress.add_task(&quot;[cyan]Downloading...&quot;, total=page_count)

        @async_retry()
        @with_semaphore(SEMAPHORE)
        async def download_task(img_url: str, hash: str, tags: list[str]):
            ext = get_url_suffix(img_url)
            img_response = await client.get(img_url)
            with open(f&quot;data/{hash}{ext}&quot;, &quot;wb&quot;) as f:
                f.write(img_response.content)
            with open(f&quot;data/{hash}.txt&quot;, &quot;w&quot;) as f:
                f.write(&quot;, &quot;.join(tags))
            progress.update(task, advance=1)

        @async_retry()
        @with_semaphore(SEMAPHORE)
        async def download_page(pid: int):
            response = await client.get(
                f&quot;https://gelbooru.com/index.php?page=dapi&amp;amp;s=post&amp;amp;q=index&amp;amp;pid={pid}&amp;amp;json=1&amp;amp;tags={TAGS}&amp;amp;api_key={API_KEY}&amp;amp;user_id={UID}&quot;
            )
            tasks = [
                download_task(post[&quot;file_url&quot;], post[&quot;md5&quot;], post[&quot;tags&quot;].split())
                for post in response.json()[&quot;post&quot;]
            ]
            await asyncio.gather(*tasks)

        tasks = [download_page(pid) for pid, _ in enumerate(range(0, page_count, 100))]
        await asyncio.gather(*tasks)
        progress.stop()


if __name__ == &quot;__main__&quot;:
    asyncio.run(main())

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;/details&amp;gt;&lt;/p&gt;
&lt;h2&gt;处理图片&lt;/h2&gt;
&lt;p&gt;有了图片素材后, 要对图片进行去重与打标签.&lt;/p&gt;
&lt;h3&gt;去重&lt;/h3&gt;
&lt;p&gt;我使用 Python 脚本进行简单的去重, 参考脚本如下:&lt;/p&gt;
&lt;p&gt;&amp;lt;details&amp;gt;
&amp;lt;summary&amp;gt;展开查看参考脚本&amp;lt;/summary&amp;gt;&lt;/p&gt;
&lt;p&gt;需要安装的库: &lt;code&gt;opencv-python scikit-image rich annoy numpy&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from typing import Optional
import cv2
import shutil
from pathlib import Path
from skimage.metrics import structural_similarity as ssim
from rich.progress import Progress
from rich.console import Console
from annoy import AnnoyIndex
import numpy as np

RAW_DIR = Path(&quot;data/step-1-deduplication&quot;)
DUP_DIR = RAW_DIR / &quot;duplicate&quot;
INV_DIR = RAW_DIR / &quot;invalid&quot;

DUP_DIR.mkdir(exist_ok=True)
INV_DIR.mkdir(exist_ok=True)

IMG_EXTS = {&quot;.jpg&quot;, &quot;.jpeg&quot;, &quot;.png&quot;, &quot;.bmp&quot;, &quot;.gif&quot;, &quot;.tiff&quot;, &quot;.webp&quot;}
SIMILARITY_THRESHOLD = 0.95  # 精确判断阈值（SSIM）
VECTOR_SIZE = 128  # ORB向量长度
NEAREST_NEIGHBORS = 5  # 查询最近邻数量

console = Console()


def is_valid_image(path: Path) -&amp;gt; bool:
    try:
        img = cv2.imread(str(path))
        return img is not None
    except Exception:
        return False


def load_gray_image(path: Path, size=256):
    img = cv2.imread(str(path), cv2.IMREAD_GRAYSCALE)
    if img is not None:
        img = cv2.resize(img, (size, size))
    return img


def compare_images(img1, img2):
    return ssim(img1, img2)


def move_with_txt(src: Path, dst_dir: Path, keep: Optional[Path] = None):
    dst_dir.mkdir(exist_ok=True)
    dst_path = dst_dir / src.name
    shutil.move(str(src), dst_path)

    src_txt = src.with_suffix(&quot;.txt&quot;)
    dst_txt = dst_path.with_suffix(&quot;.txt&quot;)

    if src_txt.exists():
        shutil.move(str(src_txt), dst_txt)
        # 如果有保留的文件但它没有标签文件，则复制一份
        if keep is not None:
            keep_txt = keep.with_suffix(&quot;.txt&quot;)
            if not keep_txt.exists():
                shutil.copy(dst_txt, keep_txt)
                console.log(f&quot;[TXT-COPIED] {src_txt} → {keep_txt}&quot;)


def get_orb_vector(path: Path):
    img = load_gray_image(path)
    if img is None:
        return np.zeros(VECTOR_SIZE, dtype=np.float32)
    orb = cv2.ORB_create()
    kp, des = orb.detectAndCompute(img, None)
    if des is None:
        return np.zeros(VECTOR_SIZE, dtype=np.float32)
    vec = des.flatten()
    if len(vec) &amp;lt; VECTOR_SIZE:
        vec = np.pad(vec, (0, VECTOR_SIZE - len(vec)))
    else:
        vec = vec[:VECTOR_SIZE]
    return vec.astype(np.float32)


def main():
    files = [
        f for f in RAW_DIR.iterdir() if f.is_file() and f.suffix.lower() in IMG_EXTS
    ]
    vectors = []
    valid_files: list[Path] = []

    # 1. 构建特征向量
    with Progress(console=console) as progress:
        task = progress.add_task(&quot;[cyan]Extracting features...&quot;, total=len(files))
        for file in files:
            progress.update(task, description=f&quot;[cyan]Processing {file.name}&quot;)
            if not is_valid_image(file):
                move_with_txt(file, INV_DIR)
                console.log(f&quot;[INVALID] {file}&quot;)
                progress.advance(task)
                continue
            vec = get_orb_vector(file)
            vectors.append(vec)
            valid_files.append(file)
            progress.advance(task)

    if not valid_files:
        console.log(&quot;No valid images found.&quot;)
        return

    # 2. 构建 Annoy 索引
    dim = VECTOR_SIZE
    index = AnnoyIndex(dim, &quot;euclidean&quot;)
    for i, vec in enumerate(vectors):
        index.add_item(i, vec)
    index.build(10)  # 可调
    processed_indices = set()
    duplicate_map = {}
    console.log(f&quot;Annoy index built with {len(valid_files)} images.&quot;)

    with Progress(console=console) as progress:
        task = progress.add_task(&quot;[green]Finding duplicates...&quot;, total=len(valid_files))
        for i, file in enumerate(valid_files):
            if file in processed_indices or not file.exists():
                progress.advance(task)
                continue
            progress.update(task, description=f&quot;[green]Checking {file.name}&quot;)
            if i in processed_indices:
                progress.advance(task)
                continue

            # 查询最近邻
            nearest = index.get_nns_by_item(i, NEAREST_NEIGHBORS)
            found_duplicate = False
            img_gray = load_gray_image(file)
            for j in nearest:
                if j == i or j in processed_indices:
                    continue
                other_file = valid_files[j]
                if not other_file.exists():
                    processed_indices.add(j)
                    continue
                other_gray = load_gray_image(other_file)
                score = compare_images(img_gray, other_gray)
                if score &amp;gt;= SIMILARITY_THRESHOLD:
                    # 保留体积更大的
                    if file.stat().st_size &amp;gt;= other_file.stat().st_size:
                        move_with_txt(other_file, DUP_DIR, keep=file)
                        console.log(
                            f&quot;[DUPLICATE] {other_file} → {DUP_DIR}, kept {file} (SSIM={score:.3f})&quot;
                        )
                        duplicate_map[DUP_DIR / other_file.name] = file
                        processed_indices.add(j)
                    else:
                        move_with_txt(file, DUP_DIR, keep=other_file)
                        console.log(
                            f&quot;[DUPLICATE] {file} → {DUP_DIR}, kept {other_file} (SSIM={score:.3f})&quot;
                        )
                        found_duplicate = True
                        break
            if not found_duplicate:
                processed_indices.add(i)
            progress.advance(task)
    for k, v in duplicate_map.items():
        console.log(f&quot;{k} keep: {v}&quot;)
    print(duplicate_map)


if __name__ == &quot;__main__&quot;:
    main()

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;/details&amp;gt;&lt;/p&gt;
&lt;p&gt;使用时请根据实际情况修改配置.&lt;/p&gt;
&lt;h3&gt;格式转换&lt;/h3&gt;
&lt;p&gt;如果图片并非 png 格式, 可以使用 &lt;a href=&quot;https://github.com/AUTOMATIC1111/stable-diffusion-webui&quot;&gt;Stable Diffusion WebUI&lt;/a&gt; 的后期处理功能, 取消勾选所有功能, 只填写输入输出目录, 跑一遍之后图片就全为 png 格式了.&lt;/p&gt;
&lt;p&gt;这里无须提前裁剪图片, 在 lora-script 训练时会自动裁剪.&lt;/p&gt;
&lt;h3&gt;打标签&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://gelbooru.com/&quot;&gt;Gelbooru&lt;/a&gt; 爬下来的图片自带标签, 所以只对番剧截图进行打标签.&lt;/p&gt;
&lt;p&gt;由于习惯原因, 我使用 &lt;a href=&quot;https://github.com/AUTOMATIC1111/stable-diffusion-webui&quot;&gt;Stable Diffusion WebUI&lt;/a&gt; 进行打标签.
也可以选择较新的其他工具.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;0.png&quot; alt=&quot;stable diffusion webui&quot; /&gt;&lt;/p&gt;
&lt;p&gt;在此页面填写输入与输出目录, 其余配置保持默认即可.&lt;/p&gt;
&lt;p&gt;完成后你应该获得与图片同名的 txt 文件, 里面存储该图片的标签.&lt;/p&gt;
&lt;h2&gt;训练&lt;/h2&gt;
&lt;h3&gt;准备&lt;/h3&gt;
&lt;p&gt;接下来就是最关键的训练了. 由于没有合适的显卡, 我使用 &lt;a href=&quot;https://www.autodl.com&quot;&gt;AutoDL&lt;/a&gt; 组一台示例做训练机器.&lt;/p&gt;
&lt;p&gt;登录并注册 &lt;a href=&quot;https://www.autodl.com&quot;&gt;AutoDL&lt;/a&gt;, 在“控制台”页面选择“租用新实例”.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;1.png&quot; alt=&quot;autodl console&quot; /&gt;&lt;/p&gt;
&lt;p&gt;显卡建议选 4090, 一张即可.
5090 由于 CUDA 太新镜像不支持, 选择多张 4090 时 lora-script 似乎无法识别, 故选择一张4090.&lt;/p&gt;
&lt;p&gt;选择好配置后下滑到镜像页面, 选择“社区镜像”, 搜索 “lora-train” 选择第一个即可.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;2.png&quot; alt=&quot;autodl images&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这个镜像的具体使用教程可以看 &lt;a href=&quot;https://www.bilibili.com/read/cv24050162/&quot;&gt;这个文章&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;镜像创建完成后打开 JupyterLab 更新并运行项目, 等待依赖安装完成.&lt;/p&gt;
&lt;h3&gt;上传图片&lt;/h3&gt;
&lt;p&gt;将图片上传到镜像合适的位置.&lt;/p&gt;
&lt;p&gt;如果你的图片较多, 建议上传到 &lt;code&gt;/autodl-tmp/&lt;/code&gt; 目录下的文件夹. 不过记得之后配置环节要填写正确的图片文件夹.&lt;/p&gt;
&lt;p&gt;图片目录应使用如下结构:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Folder/
    {count}_{concept_name}/
        image1.png
        image1.txt
        ...
    {count}_{concept_name2}/
        ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;即文件夹下的子文件夹命名为: &lt;code&gt;重复数_概念名&lt;/code&gt;, 概念名即角色画风等, 我这里只训练 &quot;shirosaki_hana” 一个概念, 所以我的目录为:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/autodl-tmp/train/
    5_shirosaki_hana/
        ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;重复数一般用 5~8 即可 (当然也不是绝对的, 可以多次尝试寻找合适值).&lt;/p&gt;
&lt;h3&gt;配置&lt;/h3&gt;
&lt;p&gt;使用&lt;a href=&quot;https://www.bilibili.com/read/cv24050162/&quot;&gt;刚才的文章&lt;/a&gt;提到的&lt;a href=&quot;https://pan.quark.cn/s/4e4e46826d48&quot;&gt;端口转发器&lt;/a&gt;, 填写配置后启动. 之后按照端口打开对应网页.&lt;/p&gt;
&lt;p&gt;在 SD-Trainer 网页中即可配置 LoRA 训练参数.&lt;/p&gt;
&lt;p&gt;我建议使用 sdxl 或 illustrious 做底模训练. 这里我使用自带的 sd_xl_base_1.0 做底模, illustrious 属于 sdxl 的变体, 这里选择此模型做底模可以最大化提高泛用型.&lt;/p&gt;
&lt;p&gt;在 SD-Trainer 网页中选择 LoRA 训练一项, 加载完成后选择“专家”.&lt;/p&gt;
&lt;p&gt;这里使用专家模式可以配置更多选项, 灵活性更高.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;3.png&quot; alt=&quot;Lora-Trainer&quot; /&gt;&lt;/p&gt;
&lt;p&gt;分享一下我使用的配置, 可以作参考. 复制并粘贴到 toml 文件中, 在 Lora-Trainer 中选择导入配置文件即可使用我的配置.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;model_train_type = &quot;sdxl-lora&quot;
pretrained_model_name_or_path = &quot;./sd-models/sd_xl_base_1.0.safetensors&quot;
train_data_dir = &quot;../autodl-tmp/train&quot;
prior_loss_weight = 1
resolution = &quot;512,512&quot;
enable_bucket = true
min_bucket_reso = 256
max_bucket_reso = 1024
bucket_reso_steps = 64
bucket_no_upscale = true
output_name = &quot;shirosakihana-sdxl-v2&quot;
output_dir = &quot;./output&quot;
save_model_as = &quot;safetensors&quot;
save_precision = &quot;bf16&quot;
save_every_n_epochs = 1
save_state = true
max_train_epochs = 10
train_batch_size = 4
gradient_checkpointing = false
network_train_unet_only = false
network_train_text_encoder_only = false
learning_rate = 0.0001
unet_lr = 0.0001
text_encoder_lr = 0.00001
lr_scheduler = &quot;cosine_with_restarts&quot;
lr_warmup_steps = 0
lr_scheduler_num_cycles = 1
optimizer_type = &quot;AdamW8bit&quot;
network_module = &quot;networks.lora&quot;
network_dim = 64
network_alpha = 64
randomly_choice_prompt = false
positive_prompts = &quot;(masterpiece, best quality:1.2), 1girl, arms behind back, bangs, black hair, blue dress, blush, bow, closed mouth, dress, eyebrows visible through hair, flower, hair between eyes, hair flower, hair ornament, long hair, long sleeves, looking at viewer, pink flower, red bow, sailor collar, sailor dress, school uniform, shirosaki hana, shirt, simple background, sleeveless, sleeveless dress, smile, solo, very long hair, white background, white sailor collar, white shirt, watashi ni tenshi ga maiorita!&quot;
negative_prompts = &quot;lowres, bad anatomy, bad hands, text, error, missing fingers, extra digit, fewer digits, cropped, worst quality, low quality, normal quality, jpeg artifacts,signature, watermark, username, blurry&quot;
sample_width = 512
sample_height = 512
sample_cfg = 7
sample_seed = 114514
sample_steps = 24
sample_sampler = &quot;euler_a&quot;
sample_every_n_epochs = 1
log_with = &quot;tensorboard&quot;
logging_dir = &quot;./logs&quot;
caption_extension = &quot;.txt&quot;
shuffle_caption = false
keep_tokens = 0
max_token_length = 255
seed = 1337
mixed_precision = &quot;bf16&quot;
xformers = true
lowram = false
cache_latents = true
cache_latents_to_disk = true
persistent_data_loader_workers = true

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;模型名, 训练数据目录和生成预览图的标签一定要改成自己的, 别完全照抄.&lt;/p&gt;
&lt;p&gt;配置完成后点击“开始训练”等待训练完成即可, 期间可以在 JupyterLab 中查看 output 目录下训练过程中生成的预览图查看训练效果. 也可以在 Lora-Trainer 中查看 Tensorboard 观察训练过程.&lt;/p&gt;
&lt;h2&gt;使用&lt;/h2&gt;
&lt;p&gt;训练完成后下载训练结果, 在 &lt;a href=&quot;https://www.comfy.org/&quot;&gt;ComfyUI&lt;/a&gt; 中生成几张图片测试效果.&lt;/p&gt;
&lt;p&gt;由于训练的是 sdxl LoRA, 底模可以选择 sdxl 或 illustrious.&lt;/p&gt;
&lt;p&gt;这里推荐 “JANKU v5” 和 “waiNSFW” 这两个 illustrious 模型做底模.&lt;/p&gt;
&lt;h2&gt;展示&lt;/h2&gt;
&lt;p&gt;下列图片下载后拖入 &lt;a href=&quot;https://www.comfy.org/&quot;&gt;ComfyUI&lt;/a&gt; 即可获取完整工作流.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/image/2026.2.13/0.png&quot; alt=&quot;show0&quot; /&gt;
&lt;img src=&quot;/image/2026.2.13/1.png&quot; alt=&quot;show1&quot; /&gt;
&lt;img src=&quot;/image/2026.2.13/2.png&quot; alt=&quot;show2&quot; /&gt;
&lt;img src=&quot;/image/2026.2.13/3.png&quot; alt=&quot;show3&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>解决 immich 机器学习报错</title><link>https://blog.xiyang6666.top/posts/fragmented-issues/2025-6-21_%E8%A7%A3%E5%86%B3-immich-%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E6%8A%A5%E9%94%99/</link><guid isPermaLink="true">https://blog.xiyang6666.top/posts/fragmented-issues/2025-6-21_%E8%A7%A3%E5%86%B3-immich-%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E6%8A%A5%E9%94%99/</guid><pubDate>Sat, 21 Jun 2025 15:44:55 GMT</pubDate><content:encoded>&lt;h2&gt;问题描述&lt;/h2&gt;
&lt;p&gt;更新 immich 后, 使用智能搜索, 人脸检测等功能时任务无法正常执行. 观察日志, 发现大量报错:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;UnidentifiedImageError: cannot identify image file &amp;lt;_io.BytesIO object at 0x7cfd32cf95d0&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;解决方法&lt;/h2&gt;
&lt;p&gt;删除 immich machine learning 的 volume, 重启之后再运行智能搜索任务,
注意不能选缺失, 应该选全部, 否则不会自动下载模型.&lt;/p&gt;
&lt;p&gt;人脸识别同理, 选择重置. 不过之前命名过的人物数据也会被清除.&lt;/p&gt;
</content:encoded></item><item><title>忽略 python 中 libpng 的 iCCP 警告</title><link>https://blog.xiyang6666.top/posts/fragmented-issues/2025-6-1_%E5%BF%BD%E7%95%A5-python-%E4%B8%AD-libpng-%E7%9A%84-iccp-%E8%AD%A6%E5%91%8A/</link><guid isPermaLink="true">https://blog.xiyang6666.top/posts/fragmented-issues/2025-6-1_%E5%BF%BD%E7%95%A5-python-%E4%B8%AD-libpng-%E7%9A%84-iccp-%E8%AD%A6%E5%91%8A/</guid><pubDate>Sun, 01 Jun 2025 15:37:53 GMT</pubDate><content:encoded>&lt;h2&gt;问题描述&lt;/h2&gt;
&lt;p&gt;在使用 Python 处理 PNG 图片时，经常会遇到以下警告:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;libpng warning: iCCP: known incorrect sRGB profile&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;根据 &lt;a href=&quot;https://github.com/pnggroup/libpng/issues/487&quot;&gt;libpng 官方的说法&lt;/a&gt;, 图片本来就是坏的, 但是 libpng 仍然会尝试去处理它, 导致这个警告.&lt;/p&gt;
&lt;p&gt;但即使项目中没有用任何 png 图片素材, 仅仅用 Tkinter 创建一个窗口都会触发一大堆这个警告刷屏, 强迫症患者震怒.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;libpng_warnings.png&quot; alt=&quot;libpng_warnings&quot; /&gt;&lt;/p&gt;
&lt;p&gt;而且这个错误是直接调用 c 函数输出的, 十分恶心.&lt;/p&gt;
&lt;h2&gt;尝试过程&lt;/h2&gt;
&lt;p&gt;网上看了一堆垃圾教程, 没一个有用的. 总结一下这些垃圾教程教的方法:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;使用 warning 库屏蔽输出.
不可能有用, warning 库只能做到 python 层面屏蔽, 输出都不经过 python.&lt;/li&gt;
&lt;li&gt;重定向 stderr.
python 内置的 sys.stderr 本质还是经过 python 包装过的, 无法重定向 c 的输出.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;解决方法&lt;/h2&gt;
&lt;p&gt;还是重定向 stderr 输出管道, 并不优雅, 但至少没有一堆警告刷屏了.&lt;/p&gt;
&lt;p&gt;与直接用&lt;code&gt;sts.stderr = open(os.devnull, &apos;w&apos;)&lt;/code&gt; 不同, 这里需要更底层的操作.&lt;/p&gt;
&lt;p&gt;这里实现一个上下文管理器, 在进入时重定向 stderr, 离开时恢复原状.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class LibpngFilter:
    def __enter__(self):
        self.pipe_in, pipe_out = os.pipe()
        self.orig_fd = os.dup(2)
        os.dup2(pipe_out, 2)
        os.close(pipe_out)
        self.stop = False

        def reader():
            f = os.fdopen(self.pipe_in)
            for line in f:
                if self.stop:
                    break
                if line.startswith(&quot;libpng warning:&quot;): # 屏蔽逻辑, 可以根据需要修改
                    break
                sys.__stderr__.write(line)
                sys.__stderr__.flush()
            f.close()

        self.thread = threading.Thread(target=reader, daemon=True)
        self.thread.start()
        return self

    def __exit__(self, type, val, tb):
        self.stop = True
        os.dup2(self.orig_fd, 2)
        os.close(self.orig_fd)
        self.thread.join()
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;使用方法&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;with LibpngFilter():
    ...
    # 处理 png 图片的代码
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>OpenWrt 使用 Nikki dns 劫持无法解析 raw.githubusercontent.com 的问题解决方法</title><link>https://blog.xiyang6666.top/posts/fragmented-issues/2025-3-30_openwrt-%E4%BD%BF%E7%94%A8-nikki-dns-%E5%8A%AB%E6%8C%81%E6%97%A0%E6%B3%95%E8%AE%BF%E9%97%AE-rawgithubusercontentcom-%E7%9A%84%E9%97%AE%E9%A2%98%E8%A7%A3%E5%86%B3%E6%96%B9%E6%B3%95/</link><guid isPermaLink="true">https://blog.xiyang6666.top/posts/fragmented-issues/2025-3-30_openwrt-%E4%BD%BF%E7%94%A8-nikki-dns-%E5%8A%AB%E6%8C%81%E6%97%A0%E6%B3%95%E8%AE%BF%E9%97%AE-rawgithubusercontentcom-%E7%9A%84%E9%97%AE%E9%A2%98%E8%A7%A3%E5%86%B3%E6%96%B9%E6%B3%95/</guid><pubDate>Sun, 30 Mar 2025 15:51:52 GMT</pubDate><content:encoded>&lt;h2&gt;问题描述&lt;/h2&gt;
&lt;p&gt;OpenWrt 路由器上使用 Nikki 作为 TProxy 代理, 开启 dns 劫持, 无法解析 &lt;code&gt;raw.githubusercontent.com&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;1.png&quot; alt=&quot;nslookup结果&quot; /&gt;&lt;/p&gt;
&lt;p&gt;如图, 使用 &lt;code&gt;nslookup&lt;/code&gt; 命令查询 &lt;code&gt;raw.githubusercontent.com&lt;/code&gt;, 响应为 &lt;code&gt;0.0.0.0&lt;/code&gt; 其余域名均正常 &lt;s&gt;(或许是还没发现其他不正常的域名)&lt;/s&gt;.&lt;/p&gt;
&lt;p&gt;在 OpenWrt 路由器上使用 nslookup 查询 &lt;code&gt;127.0.0.1:53&lt;/code&gt; &lt;code&gt;127.0.0.1:1053&lt;/code&gt; 均正常响应.&lt;/p&gt;
&lt;p&gt;检查 Mihomo DNS 配置准确无误.&lt;/p&gt;
&lt;h2&gt;故障分析&lt;/h2&gt;
&lt;p&gt;检查 OpenWrt 的 Dnsmasq 配置, 发现 DNS redirect 功能开启.&lt;/p&gt;
&lt;p&gt;经测试后发现, 同时开启 Mihomo 的 DNS 劫持与 Dnsmasq 的 DNS 劫持时会产生上述问题. 关闭其中一个后解析正常.&lt;/p&gt;
&lt;h2&gt;解决方法&lt;/h2&gt;
&lt;p&gt;检查路由器 Dnsmasq 配置.&lt;/p&gt;
&lt;p&gt;取消勾选 “网络” -&amp;gt; “DHCP/DNS” -&amp;gt; “常规” 页面中的 &lt;code&gt;DNS redirect&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;2.png&quot; alt=&quot;dnsmasq配置&quot; /&gt;&lt;/p&gt;
&lt;p&gt;验证结果:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;3.png&quot; alt=&quot;nslookup结果&quot; /&gt;&lt;/p&gt;
&lt;p&gt;更改配置后域名被正常解析.&lt;/p&gt;
</content:encoded></item><item><title>Vmware 虚拟机无法连接主机问题解决方法</title><link>https://blog.xiyang6666.top/posts/fragmented-issues/2025-3-30_vmware-%E8%99%9A%E6%8B%9F%E6%9C%BA%E6%97%A0%E6%B3%95%E8%BF%9E%E6%8E%A5%E4%B8%BB%E6%9C%BA%E9%97%AE%E9%A2%98%E8%A7%A3%E5%86%B3%E6%96%B9%E6%B3%95/</link><guid isPermaLink="true">https://blog.xiyang6666.top/posts/fragmented-issues/2025-3-30_vmware-%E8%99%9A%E6%8B%9F%E6%9C%BA%E6%97%A0%E6%B3%95%E8%BF%9E%E6%8E%A5%E4%B8%BB%E6%9C%BA%E9%97%AE%E9%A2%98%E8%A7%A3%E5%86%B3%E6%96%B9%E6%B3%95/</guid><pubDate>Sun, 30 Mar 2025 11:27:12 GMT</pubDate><content:encoded>&lt;p&gt;省流: 切换为公用网络.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;1.png&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>解决 Thunderbird 登录 Gmail 时提示:“无法登录到服务器。可能是配置、用户名或者密码错误。”</title><link>https://blog.xiyang6666.top/posts/fragmented-issues/2025-3-9_%E8%A7%A3%E5%86%B3-thunderbird-%E7%99%BB%E5%BD%95-gmail-%E6%97%B6%E9%97%AE%E9%A2%98/</link><guid isPermaLink="true">https://blog.xiyang6666.top/posts/fragmented-issues/2025-3-9_%E8%A7%A3%E5%86%B3-thunderbird-%E7%99%BB%E5%BD%95-gmail-%E6%97%B6%E9%97%AE%E9%A2%98/</guid><pubDate>Sun, 09 Mar 2025 01:54:34 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;0.png&quot; alt=&quot;thunderbird&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;网上已有解决方法&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;删密码
详见 &lt;a href=&quot;https://www.youtube.com/watch?v=bu3QkC9HhVw&quot;&gt;Authentication Failure while connecting to server solved&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;改配置
修改 &lt;code&gt;general.useragent.compatMode.firefox&lt;/code&gt; 的值为 true.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;检查代理设置
详见 &lt;a href=&quot;https://solitorian.com/2023/10/thunderbird-gmail/&quot;&gt;[技术] Thunderbird 无法连接 Gmail&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;我都试了一遍, 均未解决问题.&lt;/p&gt;
&lt;h3&gt;最终解决方案&lt;/h3&gt;
&lt;p&gt;检查你的机场是否支持 IMAP.&lt;/p&gt;
&lt;p&gt;部分机场代理宣称支持IMAP协议，但实际可能存在协议限制或虚假宣传.&lt;/p&gt;
&lt;p&gt;在使用虚拟网卡或者部署了透明代理的情况下通过以下命令验证:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;telnet imap.gmail.com 993
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果提示无法连接或者闪一下就退出那么多半不支持 IMAP, 更换一个支持 IMAP 的机场即可.&lt;/p&gt;
&lt;p&gt;&lt;s&gt;ps: 所以无法连接为什么要提示无法登录啊(恼)&lt;/s&gt;&lt;/p&gt;
</content:encoded></item><item><title>记一次笔记本亮度调节失效的排查与修复</title><link>https://blog.xiyang6666.top/posts/fragmented-issues/2025-3-8_%E8%AE%B0%E4%B8%80%E6%AC%A1%E7%AC%94%E8%AE%B0%E6%9C%AC%E4%BA%AE%E5%BA%A6%E8%B0%83%E8%8A%82%E5%A4%B1%E6%95%88%E7%9A%84%E6%8E%92%E6%9F%A5%E4%B8%8E%E4%BF%AE%E5%A4%8D/</link><guid isPermaLink="true">https://blog.xiyang6666.top/posts/fragmented-issues/2025-3-8_%E8%AE%B0%E4%B8%80%E6%AC%A1%E7%AC%94%E8%AE%B0%E6%9C%AC%E4%BA%AE%E5%BA%A6%E8%B0%83%E8%8A%82%E5%A4%B1%E6%95%88%E7%9A%84%E6%8E%92%E6%9F%A5%E4%B8%8E%E4%BF%AE%E5%A4%8D/</guid><pubDate>Sat, 08 Mar 2025 17:10:21 GMT</pubDate><content:encoded>&lt;p&gt;重装笔记本系统后，笔记本亮度调节失效, 网上找了一堆教程都没用, 最后发现是 Nvidia App 的问题.&lt;/p&gt;
&lt;h2&gt;问题描述&lt;/h2&gt;
&lt;p&gt;可以拖动亮度条, 但屏幕亮度不变, 始终保持最高亮度.&lt;/p&gt;
&lt;h2&gt;尝试过程&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;设备管理器中禁用后重新启用显示适配器.
右键 windows 徽标, 打开设备管理器, 点击显示适配器, 禁用 Intel(R) Iris(R) Xe Graphics 后再重新启用.&lt;/li&gt;
&lt;li&gt;品牌官网下载并安装官方驱动.&lt;/li&gt;
&lt;li&gt;修改注册表
打开注册表管理器,
转到&lt;code&gt;计算机\HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\Class\{4d36e968-e325-11ce-bfc1-08002be10318}\0000&lt;/code&gt;.
修改&lt;code&gt;FeatureTestControl&lt;/code&gt;为&lt;code&gt;ffff&lt;/code&gt;,重启电脑.&lt;/li&gt;
&lt;li&gt;检查 Nvidia 系统服务是否开启.
打开任务管理器, 点击服务, 检查 Nvidia 相关服务是否开启.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;所有操作均告失败, 仍然无法调节亮度, 折腾了一天后我选择放弃.&lt;/p&gt;
&lt;h2&gt;解决方法&lt;/h2&gt;
&lt;p&gt;故障持续一周后, 一次玩 Minecraft 时发现帧率似乎有些低, 尝试点击托盘里的图标打开 NVIDIA App 调节设置时, 闪过一个黑色的窗口后就闪退了.
此时注意到尽管托盘图标常驻，但实际上 Nvidia App 仍可能处于异常状态.&lt;/p&gt;
&lt;p&gt;我选择重装 Nvidia App, 去官网下载 &lt;a href=&quot;https://www.nvidia.cn/geforce/drivers/&quot;&gt;自动更新驱动程序&lt;/a&gt; 并安装, 安装过程中主屏幕闪了一下变暗了, 滑动亮度调节条发现终于能正常调节亮度了.&lt;/p&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;如果你也遇到了笔记本亮度调节失效的问题, 尝试了网上各种方法都没用后可以先检查一下 Nvidia App 是否能正常打开, 如果不能重装该软件后也许能解决这种玄学问题.&lt;/p&gt;
</content:encoded></item><item><title>分享一个自建的服务镜像站</title><link>https://blog.xiyang6666.top/posts/sharing/2024-11-10_%E5%88%86%E4%BA%AB%E4%B8%80%E4%B8%AA%E8%87%AA%E5%BB%BA%E7%9A%84%E6%9C%8D%E5%8A%A1%E9%95%9C%E5%83%8F%E7%AB%99/</link><guid isPermaLink="true">https://blog.xiyang6666.top/posts/sharing/2024-11-10_%E5%88%86%E4%BA%AB%E4%B8%80%E4%B8%AA%E8%87%AA%E5%BB%BA%E7%9A%84%E6%9C%8D%E5%8A%A1%E9%95%9C%E5%83%8F%E7%AB%99/</guid><pubDate>Sun, 10 Nov 2024 16:36:04 GMT</pubDate><content:encoded>&lt;p&gt;&lt;a href=&quot;https://mirror.shirosakihana.moe&quot;&gt;&lt;img src=&quot;https://img.shields.io/badge/%E9%95%9C%E5%83%8F%E7%AB%99-mirror.shirosakihana.moe-blue&quot; alt=&quot;镜像站主页&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;详见 &lt;a href=&quot;https://mirror.shirosakihana.moe&quot;&gt;镜像站主页&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Docker 镜像&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;地址&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Docker Hub&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://dhub.shirosakihana.moe&quot;&gt;dhub.shirosakihana.moe&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitHub Container Registry (ghcr)&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://ghcr.shirosakihana.moe&quot;&gt;ghcr.shirosakihana.moe&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;配置步骤&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# 编辑 Docker 配置文件
sudo vim /etc/docker/daemon.json

# 添加镜像配置（保留原有配置需用逗号分隔）
{
  &quot;registry-mirrors&quot;: [
    &quot;https://dhub.shirosakihana.moe&quot;,
    &quot;https://ghcr.shirosakihana.moe&quot;
  ]
}

# 重载服务配置
sudo systemctl daemon-reload
sudo systemctl restart docker
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;拉取镜像方式对比&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# 原生拉取
docker pull nginx

# 显式指定镜像源(library)
docker pull dhub.shirosakihana.moe/library/nginx

# 显式指定镜像源(user)
docker pull ghcr.shirosakihana.moe/xiyang6666/shirosakihana_moe
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Mikanani 订阅&lt;/h2&gt;
&lt;p&gt;使用 Cloudflare Workers 部署的 Mikanani 订阅代理.&lt;/p&gt;
&lt;h3&gt;使用方法示例&lt;/h3&gt;
&lt;p&gt;原订阅链接:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;https://mikanani.me/RSS/Bangumi?bangumiId=1833&amp;amp;subgroupid=370&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;更改后:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;https://mikanani.shirosakihana.moe/RSS/Bangumi?bangumiId=1833&amp;amp;subgroupid=370&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;注意&lt;/h3&gt;
&lt;p&gt;仅代理了 &lt;code&gt;/RSS&lt;/code&gt; 目录. 方便 BT 软件使用 RSS 订阅自动追番. 在 &lt;a href=&quot;https://mikanani.me&quot;&gt;Mikanani&lt;/a&gt; 寻找并复制订阅链接的过程还需要科学上网.&lt;/p&gt;
</content:encoded></item><item><title>linux QQ 每次重启都识别为新设备登录解决方法</title><link>https://blog.xiyang6666.top/posts/fragmented-issues/2024-7-31_linux-qq-%E6%AF%8F%E6%AC%A1%E9%87%8D%E5%90%AF%E9%83%BD%E8%AF%86%E5%88%AB%E4%B8%BA%E6%96%B0%E8%AE%BE%E5%A4%87%E7%99%BB%E5%BD%95%E8%A7%A3%E5%86%B3%E6%96%B9%E6%B3%95/</link><guid isPermaLink="true">https://blog.xiyang6666.top/posts/fragmented-issues/2024-7-31_linux-qq-%E6%AF%8F%E6%AC%A1%E9%87%8D%E5%90%AF%E9%83%BD%E8%AF%86%E5%88%AB%E4%B8%BA%E6%96%B0%E8%AE%BE%E5%A4%87%E7%99%BB%E5%BD%95%E8%A7%A3%E5%86%B3%E6%96%B9%E6%B3%95/</guid><pubDate>Wed, 31 Jul 2024 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;闲来无事捣鼓了一下 Ubuntu. 发现 QQ Linux 版在安装了 docker 后每次重启都会识别为新设备登录. 时间一长登录设备列表里全是 Linux QQ 的登录记录.&lt;/p&gt;
&lt;p&gt;查询了资料后得知是 docker 的虚拟网卡会在每次重启后随机分配 mac 地址. 只要固定网卡的 mac 地址问题就解决了.&lt;/p&gt;
&lt;h2&gt;解决方法&lt;/h2&gt;
&lt;p&gt;编写一个脚本用于设置 docker0 网卡的 mac 地址.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;/usr/local/bin/fix_docker_mac.sh&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/bin/bash

sudo ip link set dev docker0 address 00:11:45:14:19:19
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;⚠ 注意 ⚠&lt;/h3&gt;
&lt;p&gt;MAC 地址 &lt;strong&gt;最高字节&lt;/strong&gt; 的 &lt;strong&gt;低第一位&lt;/strong&gt;, 表示这个 MAC 地址是单播还是多播. 0 表示单播, 1 表示组播. 设置网卡 mac 地址时该位必须为 0.&lt;/p&gt;
&lt;p&gt;编写一个系统服务在 docker.service 启动后运行脚本.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;/etc/systemd/system/fix_docker_mac.service&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[Unit]
Description=Fix Docker MAC Address
After=docker.service

[Service]
ExecStart=/usr/local/bin/fix_docker_mac.sh

[Install]
WantedBy=default.target

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;运行 &lt;code&gt;sudo systemctl enable fix_docker_mac&lt;/code&gt; 使系统服务生效.&lt;/p&gt;
&lt;p&gt;重启后 QQ 已经能正常登录了.&lt;/p&gt;
</content:encoded></item></channel></rss>