<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Yakhyokhuja Valikhujaev</title>
    <description>The latest articles on DEV Community by Yakhyokhuja Valikhujaev (@yakhyo).</description>
    <link>https://dev.to/yakhyo</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F980989%2F3e4063a5-fdaa-4ae6-b648-50f084b9c9c0.jpg</url>
      <title>DEV Community: Yakhyokhuja Valikhujaev</title>
      <link>https://dev.to/yakhyo</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/yakhyo"/>
    <language>en</language>
    <item>
      <title>Changing a Face Recognition Model Also Changes Your Threshold</title>
      <dc:creator>Yakhyokhuja Valikhujaev</dc:creator>
      <pubDate>Fri, 16 Oct 2026 09:34:28 +0000</pubDate>
      <link>https://dev.to/yakhyo/changing-a-face-recognition-model-also-changes-your-threshold-3p3n</link>
      <guid>https://dev.to/yakhyo/changing-a-face-recognition-model-also-changes-your-threshold-3p3n</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpjd9s6ruzqofrdi44bf0.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpjd9s6ruzqofrdi44bf0.jpg" alt="Face verification examples using archival portrait pairs" width="800" height="380"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Replacing a face recognition model can leave the Python code almost unchanged while changing the meaning of every stored embedding and similarity score.&lt;/p&gt;

&lt;p&gt;UniFace exposes AdaFace, ArcFace, EdgeFace, MobileFace, and SphereFace through a common recognizer interface. That makes it convenient to compare implementations. It does not make their embedding spaces or decision thresholds interchangeable.&lt;/p&gt;

&lt;p&gt;This article develops that distinction from &lt;a href="https://yakhyo.github.io/blog/2026/09/face-recognition-adaface-arcface-edgeface/" rel="noopener noreferrer"&gt;my original recognition model comparison&lt;/a&gt;. The focus here is what needs to change in a pipeline when the recognizer changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The interface stays stable; the representation changes
&lt;/h2&gt;

&lt;p&gt;The recognition path starts with an image and five alignment landmarks. The landmarks define a standardized face crop, and the recognizer maps that crop to an embedding. UniFace can return an L2-normalized vector for cosine similarity.&lt;/p&gt;

&lt;p&gt;Two models can both produce 512 values without assigning the same meaning to those coordinates. Comparing an AdaFace vector with an ArcFace vector is not a supported identity comparison. Even switching weights within a model family should be treated as a representation change unless compatibility has been established.&lt;/p&gt;

&lt;p&gt;For a stored gallery, replacing the recognizer therefore requires regenerating its embeddings and rebuilding the corresponding search index. A migration that changes only the model constructor can produce plausible-looking scores from incompatible vectors.&lt;/p&gt;

&lt;h2&gt;
  
  
  Compute a score before assigning a decision
&lt;/h2&gt;

&lt;p&gt;The following example uses an explicit ArcFace variant and requires one face in each input. It prints a similarity score without attaching an uncalibrated match label.&lt;/p&gt;

&lt;p&gt;Install the CPU runtime with &lt;code&gt;pip install "uniface[cpu]"&lt;/code&gt;, then supply two portrait images:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;uniface&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ArcFace&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;RetinaFace&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;compute_similarity&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;uniface.constants&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ArcFaceWeights&lt;/span&gt;

&lt;span class="n"&gt;detector&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;RetinaFace&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;recognizer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ArcFace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;ArcFaceWeights&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MNET&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;embed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;imread&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;image&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;FileNotFoundError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Could not read &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;faces&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;detector&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;detect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;faces&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Expected one face in &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;, found &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;faces&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;recognizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_normalized_embedding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;faces&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;landmarks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;compute_similarity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nf"&gt;embed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;portrait_a.jpg&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;embed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;portrait_b.jpg&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;normalized&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Cosine similarity: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The score is a similarity measurement, not the probability that the images show the same person. A decision threshold is a separate part of the application.&lt;/p&gt;

&lt;p&gt;Selecting the highest-confidence detection would not resolve an ambiguous multi-person image: detector confidence measures detection confidence, not which subject the caller intended. Requiring exactly one face makes this example's assumption explicit.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a small comparison can tell you
&lt;/h2&gt;

&lt;p&gt;The original post compared six model configurations on five archival portraits, producing two same-person pairs and eight different-person pairs. That exercise showed how the scores changed between models while the input images stayed fixed.&lt;/p&gt;

&lt;p&gt;It did not establish a general accuracy ranking, a false-match rate for deployment, or a recommendation for a particular device. Ten pairs are too few to characterize errors across lighting, pose, age, camera quality, and population differences. The illustration above comes from a separate demo run and should not be treated as the numerical record of the comparison.&lt;/p&gt;

&lt;p&gt;A small test can check that a pipeline runs and reveal obvious score changes. Choosing a deployment threshold requires a larger, representative evaluation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Calibrate the complete pipeline
&lt;/h2&gt;

&lt;p&gt;Collect labeled same-person and different-person examples that reflect the intended inputs. Separate threshold selection from final evaluation so the reported result is not simply a fit to the calibration examples.&lt;/p&gt;

&lt;p&gt;For a rule that accepts a pair when its score is at least a threshold, increasing that threshold generally reduces false matches while increasing missed matches. Evaluate both errors and choose an operating point based on the application's requirements. A value copied from a demo or another model has no such justification.&lt;/p&gt;

&lt;p&gt;The detector and preprocessing belong in that evaluation. Different landmarks change alignment; crop geometry, color conventions, and image quality can also affect embeddings. Keep the detector, alignment method, recognizer weights, and preprocessing fixed when comparing scores.&lt;/p&gt;

&lt;p&gt;For one-to-many search, evaluate the search workflow with a gallery of representative size as well. A pairwise threshold study alone does not describe the chance of an incorrect identification across many candidates.&lt;/p&gt;

&lt;h2&gt;
  
  
  Plan model changes as data migrations
&lt;/h2&gt;

&lt;p&gt;Record the recognizer variant, weight checksum, and preprocessing configuration alongside the embedding collection. Build the replacement gallery separately, recalibrate its decision rule, and validate it before switching the application to the new index.&lt;/p&gt;

&lt;p&gt;Runtime measurements need the same care. Weight-file size describes storage, not latency. Measure preprocessing, alignment, inference, and search on the intended hardware and execution provider. The laptop timings in the original post are observations from that setup, not performance promises for another device.&lt;/p&gt;

&lt;p&gt;A shared API reduces the code needed to compare recognizers. The data migration and evaluation remain part of the change.&lt;/p&gt;

&lt;p&gt;See UniFace's &lt;a href="https://yakhyo.github.io/uniface/modules/recognition/" rel="noopener noreferrer"&gt;recognition documentation&lt;/a&gt;, &lt;a href="https://yakhyo.github.io/uniface/concepts/thresholds-calibration/" rel="noopener noreferrer"&gt;threshold calibration guide&lt;/a&gt;, and &lt;a href="https://yakhyo.github.io/uniface/license-attribution/" rel="noopener noreferrer"&gt;model licence terms&lt;/a&gt; before selecting a configuration for deployment.&lt;/p&gt;

</description>
      <category>python</category>
      <category>machinelearning</category>
      <category>computervision</category>
      <category>ai</category>
    </item>
    <item>
      <title>BiSeNet or XSeg? Choosing the Right Face Mask for an Image Pipeline</title>
      <dc:creator>Yakhyokhuja Valikhujaev</dc:creator>
      <pubDate>Fri, 16 Oct 2026 09:34:25 +0000</pubDate>
      <link>https://dev.to/yakhyo/bisenet-or-xseg-choosing-the-right-face-mask-for-an-image-pipeline-3p08</link>
      <guid>https://dev.to/yakhyo/bisenet-or-xseg-choosing-the-right-face-mask-for-an-image-pipeline-3p08</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8f7tyszxdxeilk6tvhju.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8f7tyszxdxeilk6tvhju.jpg" alt="BiSeNet face parsing separating facial components into labeled regions" width="800" height="454"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A face parser can return a map of facial parts or a mask of the face as a whole. Those outputs can look similar in a visualization, but they support different operations.&lt;/p&gt;

&lt;p&gt;If an editor needs to select hair or lips, it needs region labels. If a compositing step needs a face-shaped selection, a single mask may be sufficient. Choosing between BiSeNet and XSeg starts with that distinction, before comparing model size or inference time.&lt;/p&gt;

&lt;p&gt;This article adapts &lt;a href="https://yakhyo.github.io/blog/2026/09/bisenet-vs-xseg-face-parsing/" rel="noopener noreferrer"&gt;my original BiSeNet/XSeg comparison&lt;/a&gt; into an implementation guide for UniFace. It retains the output examples while separating API behavior from observations on a small set of portraits.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two outputs with different meanings
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Property&lt;/th&gt;
&lt;th&gt;BiSeNet in UniFace&lt;/th&gt;
&lt;th&gt;XSeg in UniFace&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Output&lt;/td&gt;
&lt;td&gt;Integer class map, IDs 0–18&lt;/td&gt;
&lt;td&gt;Floating-point mask, values 0–1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Selection&lt;/td&gt;
&lt;td&gt;Individual regions such as hair, skin, and lips&lt;/td&gt;
&lt;td&gt;A face region, without separate part labels&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Input to the normal parsing call&lt;/td&gt;
&lt;td&gt;Face-centered BGR crop&lt;/td&gt;
&lt;td&gt;BGR image and five alignment landmarks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Output coordinates&lt;/td&gt;
&lt;td&gt;Same dimensions as the supplied crop&lt;/td&gt;
&lt;td&gt;Warped back to the supplied image dimensions&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;BiSeNet's 19 classes include background, skin, eyes, eyebrows, nose, lips, and hair. A selection is a comparison against one or more class IDs. XSeg supplies one mask; it cannot tell the application which selected pixels are lips or skin.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdemty91intsy08j741tk.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdemty91intsy08j741tk.jpg" alt="XSeg face mask and a cutout alongside the input portrait" width="800" height="313"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this example, XSeg's mask excludes the hair. It should not be treated as a full portrait or head mask. If the task is separating a person, including hair, from a background, portrait matting is a separate option.&lt;/p&gt;

&lt;h2&gt;
  
  
  Coordinate space is part of the interface
&lt;/h2&gt;

&lt;p&gt;A mask is only useful if it lines up with the pixels being edited. BiSeNet returns labels in the coordinate system of its input crop. XSeg's normal parsing call aligns the face using landmarks, runs inference, then transforms the result back into the original image.&lt;/p&gt;

&lt;p&gt;The following example makes those two coordinate systems explicit. Install UniFace with the CPU runtime using &lt;code&gt;pip install "uniface[cpu]"&lt;/code&gt; and supply a portrait containing one face.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;numpy&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;uniface&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BiSeNet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;RetinaFace&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;XSeg&lt;/span&gt;

&lt;span class="n"&gt;image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;imread&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;portrait.jpg&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;image&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;FileNotFoundError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Could not read portrait.jpg&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;faces&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;RetinaFace&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;detect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;faces&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Expected one face, found &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;faces&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;face&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;faces&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;# Expand the detector box to retain more hair and surrounding context.
&lt;/span&gt;&lt;span class="n"&gt;x1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;face&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bbox&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;bw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bh&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x2&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;x1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y2&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;y1&lt;/span&gt;
&lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;left&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mf"&gt;0.2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;bw&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;span class="n"&gt;top&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;y1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mf"&gt;0.4&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;bh&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;span class="n"&gt;right&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ceil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x2&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;0.2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;bw&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;span class="n"&gt;bottom&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ceil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;y2&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;0.2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;bh&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;span class="n"&gt;crop&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;bottom&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;right&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;labels&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BiSeNet&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;crop&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;hair_in_crop&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;labels&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;17&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;astype&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uint8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt;
&lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;imwrite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;hair_crop.png&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hair_in_crop&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;face_mask&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;XSeg&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;landmarks&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;face&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;landmarks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;imwrite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;face_mask.png&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;face_mask&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;astype&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uint8&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The crop margins are a preprocessing choice, not a guarantee that every hairstyle fits. The saved hair mask belongs to the expanded crop. To position it in the full frame, use the recorded &lt;code&gt;left&lt;/code&gt; and &lt;code&gt;top&lt;/code&gt; offsets. The XSeg mask already has the full image dimensions.&lt;/p&gt;

&lt;p&gt;UniFace also exposes &lt;code&gt;XSeg.parse_aligned()&lt;/code&gt; for callers that already have an aligned crop. That path returns a mask in crop coordinates and does not perform the full-image transformation.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the example portraits can establish
&lt;/h2&gt;

&lt;p&gt;The original comparison examined three faces. In that small sample, the larger BiSeNet backbone did not consistently preserve more detail, and XSeg produced an almost binary mask despite its floating-point output.&lt;/p&gt;

&lt;p&gt;These are observations about those examples. They do not establish a general backbone ranking or a minimum usable face size. Counting which classes appear also does not measure whether the boundaries are correct; that requires reference annotations and a segmentation metric.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fd96eh9zt1xnnzzrg5ayc.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fd96eh9zt1xnnzzrg5ayc.jpg" alt="XSeg output on a portrait with a scarf covering much of the face" width="800" height="313"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This occluded portrait illustrates another evaluation question: does the mask include the obstruction? The example is worth inspecting, but one scarf image does not establish performance on hands, glasses, or other occlusions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Match the evaluation to the edit
&lt;/h2&gt;

&lt;p&gt;For region-specific editing, evaluate the classes the editor actually uses and inspect boundaries at the intended image resolution. For face compositing, examine mask coverage, alignment errors, and leakage into hair or foreground objects.&lt;/p&gt;

&lt;p&gt;A value between zero and one is not evidence of physically accurate transparency or a calibrated probability. Smoothing a face mask can soften an edge; it does not recover hair detail or turn the output into a portrait alpha matte.&lt;/p&gt;

&lt;p&gt;BiSeNet and XSeg provide different selections. The useful comparison is whether each output supports the next image operation with acceptable errors on representative inputs.&lt;/p&gt;

&lt;p&gt;See the &lt;a href="https://yakhyo.github.io/uniface/modules/parsing/" rel="noopener noreferrer"&gt;parsing API documentation&lt;/a&gt;, the &lt;a href="https://github.com/yakhyo/face-parsing" rel="noopener noreferrer"&gt;BiSeNet implementation and weights&lt;/a&gt;, and the &lt;a href="https://github.com/yakhyo/face-segmentation" rel="noopener noreferrer"&gt;XSeg ONNX repository&lt;/a&gt; for implementation details. Check the &lt;a href="https://yakhyo.github.io/uniface/license-attribution/" rel="noopener noreferrer"&gt;model licence terms&lt;/a&gt; before deployment.&lt;/p&gt;

</description>
      <category>python</category>
      <category>machinelearning</category>
      <category>computervision</category>
      <category>ai</category>
    </item>
    <item>
      <title>UniFace: Face Analysis Beyond the Bounding Box</title>
      <dc:creator>Yakhyokhuja Valikhujaev</dc:creator>
      <pubDate>Thu, 01 Jan 2026 16:18:30 +0000</pubDate>
      <link>https://dev.to/yakhyo/uniface-a-production-ready-onnx-first-face-analysis-toolkit-in-python-1563</link>
      <guid>https://dev.to/yakhyo/uniface-a-production-ready-onnx-first-face-analysis-toolkit-in-python-1563</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpm2horfrgd71ah21l0e6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpm2horfrgd71ah21l0e6.png" alt="UniFace face analysis library for Python" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A bounding box tells you where a face is. What happens next depends on the application. A photo search tool needs a representation it can compare. A portrait editor needs the boundary between skin, hair, and background. A video workflow may need to follow a face across frames or obscure it before the footage is shared.&lt;/p&gt;

&lt;p&gt;UniFace is an open-source Python library for face detection, recognition, facial geometry, segmentation, and tracking. Most neural models run through ONNX Runtime. Developers can use the modules individually or combine them into a pipeline.&lt;/p&gt;

&lt;p&gt;This article examines how detection results feed recognition, geometry, and image processing. The images are selected examples from the project; they illustrate outputs rather than measure accuracy, robustness, or speed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fifj7r6dl4ht5iexl07pr.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fifj7r6dl4ht5iexl07pr.gif" width="200" alt="UniFace animated overview of nine face analysis tasks" height="113"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;See nine tasks running on video in the &lt;a href="https://raw.githubusercontent.com/yakhyo/uniface/main/assets/demo/uniface_hero.webp" rel="noopener noreferrer"&gt;UniFace animated demo&lt;/a&gt;: detection, landmarks, face mesh, tracking, recognition, attributes, head pose, face parsing, and anonymization.&lt;/p&gt;

&lt;h2&gt;
  
  
  Detection is the beginning of a pipeline
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frio2ve5wfk5qqr2wi8lx.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frio2ve5wfk5qqr2wi8lx.jpg" alt="Face detections and alignment keypoints produced with UniFace" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In UniFace, a detection becomes a structured &lt;code&gt;Face&lt;/code&gt; object containing a bounding box, a confidence score, and, for compatible detectors, alignment landmarks. That gives subsequent stages a common starting point.&lt;/p&gt;

&lt;p&gt;Alignment matters because a recognition model should compare faces in a consistent pose and crop. The landmarks around the eyes, nose, and mouth provide reference points for that transformation. Small differences in preprocessing can affect the embedding that comes out, even when the underlying recognition model is unchanged.&lt;/p&gt;

&lt;p&gt;UniFace handles this connection between detection and recognition. The detector locates the face; the recognizer uses its landmarks to align the image before extracting a vector. Developers can configure the detector and recognizer separately, provided the detector supplies the alignment landmarks required by the recognizer.&lt;/p&gt;

&lt;p&gt;That separation is also useful when recognition has no role in the application. A face-blurring workflow can stop at detection and pass the results directly to anonymization.&lt;/p&gt;

&lt;h2&gt;
  
  
  A face has more than one useful representation
&lt;/h2&gt;

&lt;p&gt;An embedding is useful when the question is whether two face images are similar. A recognition model maps an aligned face to a numerical vector that can be compared with another vector or searched in a collection. UniFace supports recognizers including ArcFace, AdaFace, and EdgeFace, along with an optional FAISS-backed store for embedding search.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhdut5uwsvfewj2kvewkl.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhdut5uwsvfewj2kvewkl.jpg" alt="Face verification examples showing image pairs and their similarity scores" width="800" height="380"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Those scores still need interpretation. A threshold suitable for organizing a personal photo collection may be unsuitable for a verification workflow. Model choice, image quality, and the cost of false matches all influence that decision; a demo score cannot settle it for every application.&lt;/p&gt;

&lt;p&gt;Geometry answers a different set of questions. A few alignment landmarks describe the broad arrangement of a face, while dense landmarks describe its contours in much greater detail. UniFace includes 68-, 98-, and 106-point landmark options, as well as a 468- or 478-point face mesh with relative depth.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3jqi9f1fmlnk2t1y7iy5.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3jqi9f1fmlnk2t1y7iy5.jpg" alt="Dense face meshes showing facial contours and surface geometry" width="800" height="849"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For an overlay or an effect that follows the shape of a face, those points are often the more useful output. Head pose and gaze estimation extend this spatial view with estimates of head orientation and viewing direction.&lt;/p&gt;

&lt;p&gt;The choice of representation follows the job: vectors for comparison, landmarks for geometry, and masks when the next operation happens at the pixel level.&lt;/p&gt;

&lt;h2&gt;
  
  
  From analyzing an image to changing it
&lt;/h2&gt;

&lt;p&gt;Face parsing predicts a semantic label for each pixel. UniFace's BiSeNet models use 19 classes, including regions such as skin, hair, eyes, and lips. The resulting mask can select a component for a downstream edit.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8f7tyszxdxeilk6tvhju.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8f7tyszxdxeilk6tvhju.jpg" alt="Face parsing separating facial components into semantic regions" width="800" height="454"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A portrait editor, for example, could use the predicted skin region to select where to apply a color adjustment. The quality of that selection depends on the segmentation result. A face mask from XSeg serves a broader selection task. Portrait matting with MODNet produces an alpha matte for separating a person from the background, including partially transparent boundaries.&lt;/p&gt;

&lt;p&gt;These outputs have different meanings, even though they may look similar in a demonstration. A semantic label identifies a region; an alpha matte describes how foreground and background mix. The downstream operation needs to account for that distinction.&lt;/p&gt;

&lt;p&gt;Anonymization is another practical destination for a detection. UniFace can apply pixelation, blur, or a solid fill to detected face regions. This is useful for preparing images or video for sharing, provided the output is reviewed for missed faces and adequate coverage.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fngc92i0lkkpmaxj5hedk.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fngc92i0lkkpmaxj5hedk.jpg" alt="Face anonymization using different methods to obscure detected faces" width="798" height="179"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The architecture follows the task
&lt;/h2&gt;

&lt;p&gt;UniFace exposes individual modules alongside a higher-level &lt;code&gt;FaceAnalyzer&lt;/code&gt;. The modules give developers control over each stage; the analyzer connects detection, alignment, recognition, and optional attribute predictors in one call.&lt;/p&gt;

&lt;p&gt;A small example shows that composition. After installing &lt;code&gt;pip install "uniface[cpu]"&lt;/code&gt;, the following uses RetinaFace for detection and ArcFace for embeddings:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;uniface&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ArcFace&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;FaceAnalyzer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;RetinaFace&lt;/span&gt;

&lt;span class="n"&gt;image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;imread&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;portrait.jpg&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;image&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;FileNotFoundError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Could not read portrait.jpg&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;analyzer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FaceAnalyzer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;detector&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;RetinaFace&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;recognizer&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;ArcFace&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;face&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;analyzer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;analyze&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Face bounds:&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;face&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bbox&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;face&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;embedding&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Embedding dimensions:&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;face&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;embedding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Additional predictors can enrich each face with selected attributes. Other workflows can use the modules directly: a video application might combine detection with tracking, while a face search system might assess image quality before retaining an embedding. There is no need to run every available model on every image.&lt;/p&gt;

&lt;p&gt;ONNX Runtime provides a common inference layer for most of these components, with CPU execution and acceleration through supported CUDA or CoreML environments. Execution-provider availability and model compatibility depend on the runtime build and environment. Latency and memory use need to be measured on the target machine. The emotion module is an exception that uses optional PyTorch.&lt;/p&gt;

&lt;p&gt;Model weights download on first use, undergo SHA-256 verification, and are cached locally. They can also be prepared ahead of time for offline use. Offline execution requires the selected weights and runtime dependencies to be available on the target machine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Evaluation and limitations
&lt;/h2&gt;

&lt;p&gt;The examples here do not establish a deployment accuracy or performance level. Recognition thresholds need evaluation on representative data, including false matches and missed matches. Attribute predictions can be inaccurate and biased. Tracking IDs indicate continuity across frames, not verified identity. Face obscuring requires checking for missed detections and insufficient coverage.&lt;/p&gt;

&lt;p&gt;The same care applies to model selection. UniFace's code is MIT-licensed, while individual pretrained weights have their own terms. The &lt;a href="https://yakhyo.github.io/uniface/license-attribution/" rel="noopener noreferrer"&gt;licence and attribution guide&lt;/a&gt; documents those distinctions.&lt;/p&gt;

&lt;p&gt;UniFace provides model wrappers, preprocessing, and pipeline composition. Whether a particular configuration meets an application's requirements depends on the selected weights, input data, hardware, and evaluation criteria.&lt;/p&gt;

&lt;p&gt;Explore the examples in the &lt;a href="https://huggingface.co/spaces/yakhyo/uniface" rel="noopener noreferrer"&gt;interactive demo&lt;/a&gt;, find implementation details in the &lt;a href="https://yakhyo.github.io/uniface/" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;, or browse the models and source on &lt;a href="https://github.com/yakhyo/uniface" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>python</category>
      <category>computervision</category>
      <category>onnx</category>
      <category>machinelearning</category>
    </item>
  </channel>
</rss>
