需求背景:给你一张自拍照片,检测这个照片是否符合证件照的需求,具体需求如下:

特征名称 判断标准
pure_color 背景颜色是否为纯色,均匀的颜色返回1,有杂物或背景有多种颜色返回0
background_color 背景颜色,纯白色返回1,否则0。详细要求:比rgb(202,202,194)更白返回1
sit_upright 坐姿端正,,符合返回1,否则0。侧面照、扭头、斜视返回0
head_obscured 头部被物品遮挡(如刘海、帽子等),遮挡返回1,否则0
face_obs 面部被遮挡(如口罩、手指等),遮挡返回1,否则0
face_shadow 脸上有阴影(光照不好),有返回1,否则0
face_spots 脸上是否有白色斑点,有返回1,否则0
eye_focus 目光是否正视正前方,不能从侧面拍,符合返回1,否则0。
eye_close 眼睛是否闭上,,有一只闭上就返回1,否则0
haireye 头发是否遮住眉毛,,有返回1,否则0
glass 是否佩戴有颜色的墨镜(如蓝色或黑色墨镜),有返回1,否则0。
glassspot 佩戴的眼镜是否反光,反光返回1,否则0

需求分析:显然这不是简单的检测,需要一定的图片理解。尤其是坐姿端正,这种标签。

第一步:生成训练数据,我们使用qwen3-vl-235b-a22b-thinking 作为教师模型,进行模型打标。得到训练数据集的格式如下:

{
  "messages": [
    {
      "role": "user",
      "content": "你是一位摄影师,擅长拍摄证件照,请仔细判断下面这个证件照:<image>,判断这个照片是否符合以下要求:pure_color(背景颜色是否为纯色,均匀的颜色就返回1,有杂物或者背景颜色有多种颜色就返回0), background_color(背景颜色,如果纯白色返回 1,否则 0。例如:比rgb(202,202,194)更白返回1,比rgb(178,178,168)更黑就返回0。),sit_upright(符合下面所有要求,坐姿端正,人物的脸部完全100%正对镜头,头部必须完全正直,不能歪脖子,是就返回1 ,否返回0 ,例如侧面照,扭头,斜视返回0),head_obscured(头部被物品遮挡,比如刘海,帽子等,遮挡就返回1,否则0),face_obs(面部被遮挡,比如口罩,手指等,遮挡就返回1,否则0), face_shadow(脸上有阴影,就是光照不好,如有返回 1,否则0),face_spots(脸上是否有白色的斑点,如果有就返回1,否则返回0)。eye_focus(这个证件照,目光有没有正视正前方,符合证件照的严格要求,拍照的角度正对着脸,不能从侧面拍,符合就返回1,否则返回0。 注意出现下面的情况返回0:脖子歪了,瞳孔并没有完全居中,眼神向左,眼神向右,视线并没有完全平视正前方镜头),eye_close(眼睛是否闭上,没睁开,留个缝也算闭上,有一个闭上就是1,否则0),haireye(判断头发有没有遮住眉毛,只要有一根头发遮住眉毛就是不合格,有就返回1,否则0),glass(是否佩戴有颜色的墨镜,比如蓝色或者黑色的模型,有就返回1,否则0。如果是透明的镜片返回0),glassspot(佩戴的眼镜是否反光,反光就返回1,否则0)。用json格式返回.只返回json格式,不返回其他内容。"
    },
    {
      "role": "assistant",
      "content": {
        "pure_color": 1,
        "background_color": 1,
        "sit_upright": 1,
        "head_obscured": 0,
        "face_obs": 0,
        "face_shadow": 0,
        "face_spots": 0,
        "eye_focus": 1,
        "eye_close": 0,
        "haireye": 0,
        "glass": 0,
        "glassspot": 0
      }
    }
  ],
  "images": [
    "./pics/1.jpg"
  ]
}

第二步模型训练:

使用ms-swift框架,需要48GB显存,进行full训练,不建议用lora训练。:

训练代码如下:

PYTORCH_CUDA_ALLOC_CONF='expandable_segments:True' \
IMAGE_MAX_TOKEN_NUM=1024 \
CUDA_VISIBLE_DEVICES=0 \
swift sft \
    --model ./Qwen/Qwen3-VL-2B-Instruct \
    --dataset train2.jsonl \
    --load_from_cache_file true \
    --split_dataset_ratio 0.01 \
    --tuner_type full \
    --torch_dtype bfloat16 \
    --num_train_epochs 3 \
    --per_device_train_batch_size 1 \
    --per_device_eval_batch_size 1 \
    --learning_rate 1e-4 \
    --lora_rank 8 \
    --lora_alpha 32 \
    --target_modules all-linear \
    --freeze_vit true \
    --freeze_aligner true \
    --gradient_checkpointing true \
    --vit_gradient_checkpointing false \
    --gradient_accumulation_steps 2 \
    --eval_steps 100 \
    --save_steps 100 \
    --save_total_limit 2 \
    --logging_steps 5 \
    --max_length 4096 \
    --output_dir output \
    --warmup_ratio 0.05 \
    --deepspeed zero2 \
    --dataset_num_proc 4 \
    --dataloader_num_workers 4

这里我们使用freeze_vit  trye/false进行对比,测试一下效果。

最后得到的模型,我们进行本地推理:


import json
import os
import ast
import requests
from io import BytesIO
from PIL import Image, ImageDraw, ImageFont
from PIL import ImageColor
import base64
from transformers import Qwen3VLForConditionalGeneration, AutoProcessor
additional_colors = [colorname for (colorname, colorcode) in ImageColor.colormap.items()]
def vl_inference(img_url, prompt):
    with open(img_url, "rb") as image_file:
        base64_image = base64.b64encode(image_file.read()).decode("utf-8")
    messages = [
        {
            "role": "user",
            "content": [
                {
                    "type": "image",
                    "image": f"data:image/jpeg;base64,{base64_image}",
                },
                {"type": "text", "text": prompt},
            ],
        }
    ]

    # Preparation for inference
    inputs = processor.apply_chat_template(
        messages,
        tokenize=True,
        add_generation_prompt=True,
        return_dict=True,
        return_tensors="pt"
    )
    inputs = inputs.to(model.device)

    # Inference: Generation of the output
    generated_ids = model.generate(**inputs, max_new_tokens=512)
    generated_ids_trimmed = [
        out_ids[len(in_ids):] for in_ids, out_ids in zip(inputs.input_ids, generated_ids)
    ]
    output_text = processor.batch_decode(
        generated_ids_trimmed, skip_special_tokens=True, clean_up_tokenization_spaces=False
    )
    return output_text[0]



if __name__ == '__main__':
    model = Qwen3VLForConditionalGeneration.from_pretrained(
        "./qwen3vl2bvitfalse", dtype="auto", device_map="auto"
    )

    processor = AutoProcessor.from_pretrained("./qwen3vl2bvitfalse")
    prompt = '你是一位摄影师,擅长拍摄证件照,请仔细判断下面这个证件照,判断这个照片是否符合以下要求:pure_color(背景颜色是否为纯色,均匀的颜色就返回1,有杂物或者背景颜色有多种颜色就返回0), background_color(背景颜色,如果纯白色返回 1,否则 0。例如:比rgb(202,202,194)更白返回1,比rgb(178,178,168)更黑就返回0。),sit_upright(符合下面所有要求,坐姿端正,人物的脸部完全100%正对镜头,头部必须完全正直,不能歪脖子,是就返回1 ,否返回0 ,例如侧面照,扭头,斜视返回0),head_obscured(头部被物品遮挡,比如刘海,帽子等,遮挡就返回1,否则0),face_obs(面部被遮挡,比如口罩,手指等,遮挡就返回1,否则0), face_shadow(脸上有阴影,就是光照不好,如有返回 1,否则0),face_spots(脸上是否有白色的斑点,如果有就返回1,否则返回0)。eye_focus(这个证件照,目光有没有正视正前方,符合证件照的严格要求,拍照的角度正对着脸,不能从侧面拍,符合就返回1,否则返回0。 注意出现下面的情况返回0:脖子歪了,瞳孔并没有完全居中,眼神向左,眼神向右,视线并没有完全平视正前方镜头),eye_close(眼睛是否闭上,没睁开,留个缝也算闭上,有一个闭上就是1,否则0),haireye(判断头发有没有遮住眉毛,只要有一根头发遮住眉毛就是不合格,有就返回1,否则0),glass(是否佩戴有颜色的墨镜,比如蓝色或者黑色的模型,有就返回1,否则0。如果是透明的镜片返回0),glassspot(佩戴的眼镜是否反光,反光就返回1,否则0)。用json格式返回.只返回json格式,不返回其他内容。'


    dir_path = '../pics'
    dlist = []
    for filename in os.listdir(dir_path):
        if filename.endswith('.jpg'):
            img_path = os.path.join(dir_path, filename)
            print(img_path)
            content_text = vl_inference(img_path, prompt)
            print(content_text)
            dlist.append([img_path, content_text])

    with open("./eval_local_2b_vitfalse.jsonl", "w", encoding="utf-8") as f:
        for item in dlist:
            # ensure_ascii=False 可以防止中文被转义为 \uXXXX
            f.write(json.dumps(item, ensure_ascii=False) + "\n")


然后我们把结果进行对比:

指标 full full_vit 解冻 2b (原始模型)
background_color 90.78% 56.68% 38.25%
pure_color 97.70% 82.49% 83.87%
eye_focus 97.24% 92.17% 92.17%
glass 100.00% 100.00% 98.16%
eye_close 98.62% 95.85% 97.70%
face_shadow 99.08% 99.08% 98.62%
face_obs 98.16% 96.31% 97.70%
glassspot 96.31% 96.31% 97.70%
face_spots 97.24% 97.24% 97.24%
sit_upright 94.93% 95.85% 95.85%
haireye 90.78% 90.78% 90.78%
head_obscured 100% 81.11% 81.11%

结果分析:background_color,haireye的准确率不高,主要是业务方的定义,白色并不是要求纯白色,而是模拟真实拍照场景的白色,有一定的灰度,这个灰度不好控制,模型容易误判。需要扩大训练数据集。

另外vit 冻结训练效果更好一些。

量化后,训练后的模型可以实现0.2秒出结果,只占用4GB显存,适合端侧部署。

Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐