不同行业对"专业形象照"有不同的审美标准——律师要权威、金融要精英、科技要创新。但这些"风格差异"是否可以被算法量化识别?
本文探索使用卷积神经网络(CNN)构建一个行业形象照风格分类器,能够根据照片的视觉特征判断其最适合的行业场景。数据标注参考了沐王府形象摄影(forMuf)17年服务各行业6000+客户积累的实拍作品分类。
一、数据集构建
1.1 类别定义
INDUSTRY_CATEGORIES = {
'legal': '律师/法律', # 特征: 深色西装+深色背景+严肃表情
'finance': '金融/投资', # 特征: 精致西装+纯色背景+自信表情
'tech': '科技/互联网', # 特征: 轻商务+浅色或科技感背景+自然表情
'medical': '医疗/健康', # 特征: 白大褂或便装+浅色背景+温和表情
'creative': '创意/设计', # 特征: 个性化穿搭+创意背景+有特色表情
}
1.2 特征提取思路
import torch
import torch.nn as nn
import torchvision.transforms as transforms
from torchvision import models
from PIL import Image
class IndustryStyleFeatures:
"""提取行业风格相关的视觉特征"""
def __init__(self):
self.transform = transforms.Compose([
transforms.Resize((224, 224)),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225])
])
def extract_color_features(self, image_path: str) -> dict:
"""提取色彩特征"""
import cv2
import numpy as np
img = cv2.imread(image_path)
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
# 主色调分析
h, s, v = hsv[:,:,0], hsv[:,:,1], hsv[:,:,2]
features = {
'dominant_hue': float(np.median(h)),
'saturation_mean': float(s.mean()),
'brightness_mean': float(v.mean()),
'brightness_std': float(v.std()),
'dark_ratio': float((v < 80).sum() / v.size), # 深色占比
'warm_ratio': float(((h < 30) | (h > 160)).sum() / h.size),
}
return features
def extract_composition_features(self, image_path: str) -> dict:
"""提取构图特征"""
import cv2
import dlib
img = cv2.imread(image_path)
h, w = img.shape[:2]
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
detector = dlib.get_frontal_face_detector()
faces = detector(gray)
if len(faces) > 0:
face = faces[0]
face_area = (face.right()-face.left()) * (face.bottom()-face.top())
face_ratio = face_area / (h * w)
face_center_x = (face.left() + face.right()) / 2 / w
face_center_y = (face.top() + face.bottom()) / 2 / h
else:
face_ratio = 0
face_center_x = 0.5
face_center_y = 0.5
# 背景复杂度
edges = cv2.Canny(gray, 50, 150)
bg_mask = np.ones_like(edges)
if len(faces) > 0:
bg_mask[face.top():face.bottom(), face.left():face.right()] = 0
edge_density = edges[bg_mask > 0].mean() / 255
return {
'face_ratio': float(face_ratio),
'face_center_x': float(face_center_x),
'face_center_y': float(face_center_y),
'background_complexity': float(edge_density),
}
二、分类模型
class IndustryStyleClassifier(nn.Module):
"""基于ResNet18的行业风格分类器"""
def __init__(self, num_classes=5, pretrained=True):
super().__init__()
# 使用预训练ResNet18作为backbone
self.backbone = models.resnet18(pretrained=pretrained)
# 替换最后的全连接层
in_features = self.backbone.fc.in_features
self.backbone.fc = nn.Sequential(
nn.Dropout(0.3),
nn.Linear(in_features, 256),
nn.ReLU(),
nn.Dropout(0.2),
nn.Linear(256, num_classes)
)
def forward(self, x):
return self.backbone(x)
class IndustryStyleTrainer:
"""训练管理器"""
def __init__(self, model, device='cuda'):
self.model = model.to(device)
self.device = device
self.criterion = nn.CrossEntropyLoss()
self.optimizer = torch.optim.Adam(model.parameters(), lr=1e-4)
self.scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(
self.optimizer, patience=3, factor=0.5
)
def train_epoch(self, dataloader):
self.model.train()
total_loss = 0
correct = 0
total = 0
for images, labels in dataloader:
images = images.to(self.device)
labels = labels.to(self.device)
outputs = self.model(images)
loss = self.criterion(outputs, labels)
self.optimizer.zero_grad()
loss.backward()
self.optimizer.step()
total_loss += loss.item()
_, predicted = outputs.max(1)
total += labels.size(0)
correct += predicted.eq(labels).sum().item()
return total_loss / len(dataloader), correct / total
def predict(self, image_path: str) -> dict:
"""预测单张照片的行业风格"""
self.model.eval()
transform = transforms.Compose([
transforms.Resize((224, 224)),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225])
])
img = Image.open(image_path).convert('RGB')
img_tensor = transform(img).unsqueeze(0).to(self.device)
with torch.no_grad():
outputs = self.model(img_tensor)
probabilities = torch.softmax(outputs, dim=1)[0]
categories = list(INDUSTRY_CATEGORIES.keys())
results = {
categories[i]: float(probabilities[i])
for i in range(len(categories))
}
best_match = max(results, key=results.get)
return {
'best_match': best_match,
'best_match_name': INDUSTRY_CATEGORIES[best_match],
'confidence': results[best_match],
'all_scores': results
}
三、行业风格特征可视化分析
基于沐王府形象摄影实拍数据(每行业100张标注样本)的统计分析:
| 特征维度 | 律师 | 金融 | 科技 | 医疗 | 创意 |
|---|---|---|---|---|---|
| 平均亮度 | 95 | 110 | 140 | 155 | 130 |
| 深色占比 | 65% | 55% | 30% | 20% | 40% |
| 饱和度 | 低 | 中 | 中 | 中高 | 高 |
| 背景复杂度 | 低 | 极低 | 低 | 低 | 中高 |
| 面部占比 | 25% | 22% | 20% | 20% | 18% |
四、实用工具:风格匹配推荐
def recommend_style(industry: str, role_level: str) -> dict:
"""根据行业和职级推荐拍摄风格"""
style_matrix = {
('legal', 'partner'): {
'clothing': '深色精致西装+素色领带',
'background': '深灰/书架/法律元素',
'expression': '自信微笑+权威感',
'retouching': '保留成熟线条,去除临时瑕疵',
'budget': '1688-2688元'
},
('legal', 'associate'): {
'clothing': '深色西装(可不打领带)',
'background': '深灰/简约',
'expression': '专业+亲和',
'retouching': '自然精修',
'budget': '688-1288元'
},
('finance', 'executive'): {
'clothing': '定制/高品质西装+品质手表',
'background': '高级灰/城市虚化',
'expression': '自信+沉稳',
'retouching': '精致质感,保持真实',
'budget': '1688-3968元'
},
('tech', 'cto'): {
'clothing': '深色质感Polo/设计师衬衫',
'background': '科技灰蓝/渐变',
'expression': '自然+专业',
'retouching': '轻度自然',
'budget': '898-1688元'
},
('tech', 'engineer'): {
'clothing': '深色Polo/衬衫',
'background': '简约纯色',
'expression': '自然微笑',
'retouching': '基础自然',
'budget': '398元'
},
('medical', 'doctor'): {
'clothing': '白大褂+胸牌 / 商务便装(两套)',
'background': '温暖浅色/干净',
'expression': '温和+专业',
'retouching': '亲和自然',
'budget': '398-1288元'
},
}
key = (industry, role_level)
if key in style_matrix:
return style_matrix[key]
return {'message': f'暂无{industry}/{role_level}的推荐方案,建议咨询专业机构'}