Files
hair/static/test_face_ratios.html
T

20 lines
6.1 KiB
HTML
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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.
<!doctype html><html lang="zh-CN"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>颞颧比与颧颞比测试</title>
<style>body{font:15px system-ui;background:#f4f6fa;color:#203047;margin:0}main{max-width:1200px;margin:auto;padding:28px}h1{font-size:25px}section,article{background:white;padding:20px;border-radius:12px;margin:16px 0}button,input{font:inherit;padding:9px}button{cursor:pointer} .grid{display:grid;grid-template-columns:minmax(0,2fr) minmax(260px,1fr);gap:20px}canvas{max-width:100%;height:auto;touch-action:none}pre{overflow:auto;max-height:300px;font-size:12px}.muted{color:#66758a;line-height:1.7}.value{font-size:28px;font-weight:700}#status{white-space:pre-wrap} @media(max-width:750px){.grid{display:block}}</style>
<main><p><a href="/cases/">查看批量案例与筛选结果</a></p><h1>颞颧比与颧颞比测试</h1><p class="muted">Ft:HL right right → HL left left(两端取平均高度,水平测量) · Zy:234 → 454<br>先按接口52统一参数扶正裁切,均按处理后图片二维直线距离计算。Ft ÷ Zy 小于 0.8 时,同时判定为颧弓外扩和太阳穴凹陷。</p>
<section><input aria-label="选择人像" type="file" id="file" accept="image/jpeg,image/png"><button id="run">开始检测</button> <button id="reset" disabled>恢复自动点位</button><p id="status" role="status">请选择单人正面照。</p></section>
<div class="grid"><section><canvas id="canvas"></canvas><p class="muted">粉色:Ft 蓝色:Zy。检测后可拖动端点校正,结果实时更新。</p></section><div><article><h2>颧弓外扩</h2><p>颞颧比 Ft ÷ Zy < 0.8</p><div class="value" id="r1">—</div><p id="s1">等待检测</p></article><article><h2>太阳穴凹陷</h2><p>判定条件 Ft ÷ Zy < 0.8(下方 Zy ÷ Ft 为参考值)</p><div class="value" id="r2">—</div><p id="s2">等待检测</p></article><p id="lengths"></p></div></div><section><h2>测量数据</h2><pre id="json">暂无数据</pre></section></main>
<script>
const $=id=>document.getElementById(id),keys=['hair_line_right_right','hair_line_left_left','face_edge_234','face_edge_454'],labels=['HL right right','HL left left','234','454'];
let img=null,points={},original={},drag=null,manual=false,busy=false;
function dist(a,b){return a&&b&&[a.x,a.y,b.x,b.y].every(Number.isFinite)?Math.hypot(a.x-b.x,a.y-b.y):null}
function update(){const left=points[keys[0]],right=points[keys[1]];if(left&&right){const y=(left.y+right.y)/2;left.y=right.y=y}const ft=dist(points[keys[0]],points[keys[1]]),zy=dist(points[keys[2]],points[keys[3]]),valid=ft>0&&zy>0;const a=valid?ft/zy:null,b=valid?zy/ft:null;
const matched = valid ? a < 0.8 : null;
$('r1').textContent=valid?a.toFixed(4):'—';$('r2').textContent=valid?b.toFixed(4):'—';$('s1').textContent=valid?(matched?'命中':'未命中'):'无法计算:点位缺失或距离为零';$('s2').textContent=valid?(matched?'命中':'未命中'):'无法计算:点位缺失或距离为零';$('lengths').textContent=`Ft:${ft?.toFixed(2)??'缺失'} px Zy:${zy?.toFixed(2)??'缺失'} px`;
$('json').textContent=JSON.stringify({source:manual?'manual':'automatic',points,ft_px:ft,zy_px:zy,temporal_zygomatic_ratio:a,zygomatic_temporal_ratio:b,zygoma_expansion:matched,temple_hollowing:matched},null,2);draw()}
function draw(){const c=$('canvas'),ctx=c.getContext('2d');ctx.clearRect(0,0,c.width,c.height);if(!img)return;ctx.drawImage(img,0,0,c.width,c.height);const scale=c.width/img.naturalWidth;for(let i=0;i<4;i++){const p=points[keys[i]],q=points[keys[i^1]],color=i<2?'#ed397a':'#1685ff';if(!p)continue;ctx.strokeStyle=color;ctx.fillStyle=color;ctx.lineWidth=3;if(i%2===0&&q){ctx.beginPath();ctx.moveTo(p.x*scale,p.y*scale);ctx.lineTo(q.x*scale,q.y*scale);ctx.stroke()}ctx.beginPath();ctx.arc(p.x*scale,p.y*scale,7,0,Math.PI*2);ctx.fill();ctx.font='bold 14px sans-serif';const t=labels[i],x=Math.max(2,Math.min(c.width-ctx.measureText(t).width-4,p.x*scale+10)),y=Math.max(17,p.y*scale-12);ctx.strokeStyle='#172033';ctx.lineWidth=4;ctx.strokeText(t,x,y);ctx.fillStyle='white';ctx.fillText(t,x,y)}}
function position(e){const r=$('canvas').getBoundingClientRect();return{x:(e.clientX-r.left)/r.width*img.naturalWidth,y:(e.clientY-r.top)/r.height*img.naturalHeight}}
$('canvas').onpointerdown=e=>{if(!img||busy)return;const p=position(e),radius=18*img.naturalWidth/$('canvas').getBoundingClientRect().width;drag=keys.find(k=>points[k]&&dist(points[k],p)<radius);if(drag)$('canvas').setPointerCapture(e.pointerId)};
$('canvas').onpointermove=e=>{if(!drag)return;const p=position(e);points[drag]={x:Math.max(0,Math.min(img.naturalWidth-1,p.x)),y:Math.max(0,Math.min(img.naturalHeight-1,p.y))};manual=true;update()};$('canvas').onpointerup=$('canvas').onpointercancel=()=>drag=null;
$('reset').onclick=()=>{points=structuredClone(original);manual=false;update()};
$('run').onclick=async()=>{const file=$('file').files[0];if(!file)return;busy=true;$('run').disabled=true;$('reset').disabled=true;points={};original={};manual=false;img=null;update();$('status').textContent='正在检测人脸与发际线…';let url;try{url=URL.createObjectURL(file);const next=new Image();next.src=url;await next.decode();img=next;$('canvas').width=Math.min(1000,img.naturalWidth);$('canvas').height=Math.round($('canvas').width*img.naturalHeight/img.naturalWidth);draw();const form=new FormData();form.append('image_file',file);const response=await fetch('/detect',{method:'POST',body:form});const data=await response.json();if(!response.ok)throw Error(data.detail||'检测失败');const processed=new Image();processed.src=data.input_image_base64;await processed.decode();img=processed;$('canvas').width=Math.min(1000,img.naturalWidth);$('canvas').height=Math.round($('canvas').width*img.naturalHeight/img.naturalWidth);points=Object.fromEntries(keys.map(k=>[k,data.points[k]??null]));original=structuredClone(points);update();$('reset').disabled=false;$('status').textContent='检测完成。可拖动点位验证公式;发际线点受刘海和头发遮挡影响。'}catch(e){$('status').textContent='检测失败:'+e.message}finally{if(url)URL.revokeObjectURL(url);busy=false;$('run').disabled=false}};
</script></html>