Files
ece.suwa3d.com/src/services/jingdong/image-inspect.ts
T
Linzm 500bdbbf66
continuous-integration/drone/push Build was killed
continuous-integration/drone/promote/prod Build is passing
修改:主体识别严格对比
2026-11-11 16:35:32 +08:00

192 lines
5.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 图片主体识别结果(由后端 jd/subject/classify 返回后规范化)
*/
export type ImageKind = 'cartoon' | 'real_photo' | 'other' | 'uncertain'
export interface ImageInspect {
imageType: ImageKind
isCartoon: boolean
personCount: number
petCount: number
/** 道具数量(若接口有返回) */
propCount: number
reviewStatus: string
conclusion: string
/** 主体识别 check_id,后续下单可用 */
checkId: number
}
export class InspectCancelled extends Error {
constructor() {
super('cancelled')
this.name = 'InspectCancelled'
}
}
function asRecord(value: unknown) {
return value && typeof value === 'object' && !Array.isArray(value)
? (value as Record<string, unknown>)
: null
}
export function mapInspectImageType(raw: unknown): ImageKind {
const text = String(raw ?? '')
.trim()
.toLowerCase()
if (text === 'cartoon' || text === '1') return 'cartoon'
if (text === 'real_photo' || text === 'real' || text === '2') return 'real_photo'
if (text === 'other') return 'other'
return 'uncertain'
}
function countField(data: Record<string, unknown>, keys: string[]) {
for (const key of keys) {
if (!(key in data)) continue
const value = Number(data[key])
if (Number.isFinite(value)) return Math.max(0, Math.trunc(value))
}
return 0
}
function hasOwnCount(data: Record<string, unknown>, keys: string[]) {
return keys.some((key) => key in data && Number.isFinite(Number(data[key])))
}
type KeywordBucket = 'person' | 'pet' | 'prop' | 'other'
/** 按 category / 中英文关键词区分人物、宠物、道具 */
function classifyKeywordBucket(item: Record<string, unknown>): KeywordBucket {
const category = Number(item.category ?? item.Category ?? 0)
if (category === 1) return 'person'
if (category === 2) return 'pet'
if (category === 3) return 'prop'
const text = `${item.cn || ''} ${item.en || ''} ${item.name || ''} ${item.label || ''}`.toLowerCase()
if (/人|人物|人脸|儿童|成人|person|people|human|face|child|man|woman|boy|girl/.test(text)) {
return 'person'
}
if (
/宠|宠物|猫|狗|兔|鸟|鼠|仓鼠|龙猫|鹦鹉|pet|cat|dog|rabbit|bird|hamster|animal/.test(
text,
)
) {
return 'pet'
}
if (/道|具|prop|道具|物件|object/.test(text)) return 'prop'
return 'other'
}
function countFromKeywords(raw: unknown): {
personCount: number
petCount: number
propCount: number
hasKeywords: boolean
} {
if (!Array.isArray(raw) || raw.length === 0) {
return { personCount: 0, petCount: 0, propCount: 0, hasKeywords: false }
}
let personCount = 0
let petCount = 0
let propCount = 0
let useful = false
raw.forEach((entry) => {
const item = asRecord(entry)
if (!item) return
const n = Number(item.count ?? item.num ?? 1)
const count = Number.isFinite(n) ? Math.max(0, Math.trunc(n)) : 1
if (count <= 0) return
const bucket = classifyKeywordBucket(item)
if (bucket === 'other') return
useful = true
if (bucket === 'person') personCount += count
else if (bucket === 'pet') petCount += count
else if (bucket === 'prop') propCount += count
})
return { personCount, petCount, propCount, hasKeywords: useful }
}
/** 规范化 classify 接口 data(拦截器已解包 code=1000 的 data) */
export function normalizeInspect(body: unknown): ImageInspect {
const root = asRecord(body) || {}
const nested = asRecord(root.data)
// 兼容完整响应 { code, data } / 已解包的 data 本体
const data =
nested &&
(nested.image_type != null ||
nested.person_count != null ||
nested.is_cartoon != null ||
nested.check_id != null ||
nested.keywords != null)
? nested
: root
const imageType = mapInspectImageType(data.image_type ?? data.imageType)
const isCartoonRaw = data.is_cartoon ?? data.isCartoon
const isCartoon =
typeof isCartoonRaw === 'boolean' ? isCartoonRaw : imageType === 'cartoon'
const fieldPerson = countField(data, [
'person_count',
'personCount',
'headcount',
'head_count',
])
const fieldPet = countField(data, ['pet_count', 'petCount'])
const fieldProp = countField(data, ['prop_count', 'propCount'])
const hasFieldPerson = hasOwnCount(data, [
'person_count',
'personCount',
'headcount',
'head_count',
])
const hasFieldPet = hasOwnCount(data, ['pet_count', 'petCount'])
const hasFieldProp = hasOwnCount(data, ['prop_count', 'propCount'])
const fromKeywords = countFromKeywords(data.keywords)
/**
* 严格按类别取数:
* - keywords 能分出人物/宠物时优先用(避免把宠物误记成人物)
* - 否则回退 person_count / pet_count
*/
let personCount = fieldPerson
let petCount = fieldPet
let propCount = fieldProp
if (fromKeywords.hasKeywords) {
personCount = fromKeywords.personCount
petCount = fromKeywords.petCount
propCount = fromKeywords.propCount
} else {
if (!hasFieldPerson) personCount = 0
if (!hasFieldPet) petCount = 0
if (!hasFieldProp) propCount = 0
}
return {
imageType,
isCartoon,
personCount,
petCount,
propCount,
reviewStatus: String(data.review_status || ''),
conclusion: String(data.conclusion || ''),
checkId: countField(data, ['check_id', 'checkId']),
}
}
export function isCartoonImage(result: ImageInspect) {
return result.isCartoon || result.imageType === 'cartoon'
}
export function figureTypeOf(result: ImageInspect) {
return result.imageType === 'real_photo' ? 1 : 0
}
export function formatSubjectFromInspect(result: ImageInspect) {
const parts: string[] = []
if (result.personCount > 0) parts.push(`${result.personCount}人`)
if (result.petCount > 0) parts.push(`${result.petCount}宠物`)
if (result.propCount > 0) parts.push(`${result.propCount}道具`)
return parts.join('+')
}