{"slug": "bisenet-or-xseg-choosing-the-right-face-mask-for-an-image-pipeline", "title": "BiSeNet or XSeg? Choosing the Right Face Mask for an Image Pipeline", "summary": "A developer adapted a BiSeNet-versus-XSeg face-parsing comparison into an implementation guide for the UniFace library, showing that BiSeNet returns a 19-class integer label map for region selection while XSeg returns a single floating-point face mask aligned to full-image coordinates. The guide provides working Python code for both paths, including crop-margin handling and the XSeg.parse_aligned() variant, and notes that in a three-face sample the larger BiSeNet backbone did not consistently preserve more detail and XSeg produced an almost binary mask.", "body_md": "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.\n\nIf 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.\n\nThis article adapts [my original BiSeNet/XSeg comparison](https://yakhyo.github.io/blog/2026/09/bisenet-vs-xseg-face-parsing/) into an implementation guide for UniFace. It retains the output examples while separating API behavior from observations on a small set of portraits.\n\n| Property | BiSeNet in UniFace | XSeg in UniFace | \n|---|---|---|\n| Output | Integer class map, IDs 0–18 | Floating-point mask, values 0–1 | \n| Selection | Individual regions such as hair, skin, and lips | A face region, without separate part labels | \n| Input to the normal parsing call | Face-centered BGR crop | BGR image and five alignment landmarks | \n| Output coordinates | Same dimensions as the supplied crop | Warped back to the supplied image dimensions | \n\nBiSeNet'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.\n\nIn 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.\n\nA 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.\n\nThe following example makes those two coordinate systems explicit. Install UniFace with the CPU runtime using `pip install \"uniface[cpu]\"` and supply a portrait containing one face.\n\n``` python\nimport cv2\nimport numpy as np\nfrom uniface import BiSeNet, RetinaFace, XSeg\n\nimage = cv2.imread('portrait.jpg')\nif image is None:\n    raise FileNotFoundError('Could not read portrait.jpg')\n\nfaces = RetinaFace().detect(image)\nif len(faces) != 1:\n    raise ValueError(f'Expected one face, found {len(faces)}')\nface = faces[0]\n\n# Expand the detector box to retain more hair and surrounding context.\nx1, y1, x2, y2 = face.bbox[:4]\nbw, bh = x2 - x1, y2 - y1\nheight, width = image.shape[:2]\nleft = max(0, int(np.floor(x1 - 0.2 * bw)))\ntop = max(0, int(np.floor(y1 - 0.4 * bh)))\nright = min(width, int(np.ceil(x2 + 0.2 * bw)))\nbottom = min(height, int(np.ceil(y2 + 0.2 * bh)))\ncrop = image[top:bottom, left:right]\n\nlabels = BiSeNet().parse(crop)\nhair_in_crop = (labels == 17).astype(np.uint8) * 255\ncv2.imwrite('hair_crop.png', hair_in_crop)\n\nface_mask = XSeg().parse(image, landmarks=face.landmarks)\ncv2.imwrite('face_mask.png', (face_mask * 255).astype(np.uint8))\n```\n\nThe 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 `left` and `top` offsets. The XSeg mask already has the full image dimensions.\n\nUniFace also exposes `XSeg.parse_aligned()` for callers that already have an aligned crop. That path returns a mask in crop coordinates and does not perform the full-image transformation.\n\nThe 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.\n\nThese 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.\n\nThis 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.\n\nFor 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.\n\nA 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.\n\nBiSeNet and XSeg provide different selections. The useful comparison is whether each output supports the next image operation with acceptable errors on representative inputs.\n\nSee the [parsing API documentation](https://yakhyo.github.io/uniface/modules/parsing/), the [BiSeNet implementation and weights](https://github.com/yakhyo/face-parsing), and the [XSeg ONNX repository](https://github.com/yakhyo/face-segmentation) for implementation details. Check the [model licence terms](https://yakhyo.github.io/uniface/license-attribution/) before deployment.", "url": "https://wpnews.pro/news/bisenet-or-xseg-choosing-the-right-face-mask-for-an-image-pipeline", "canonical_source": "https://dev.to/yakhyo/bisenet-or-xseg-choosing-the-right-face-mask-for-an-image-pipeline-3p08", "published_at": "2026-10-16 09:34:25+00:00", "updated_at": "2026-10-16 09:38:26.114209+00:00", "lang": "en", "topics": ["computer-vision", "ai-tools", "machine-learning", "developer-tools"], "entities": ["UniFace", "BiSeNet", "XSeg", "RetinaFace", "OpenCV"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/bisenet-or-xseg-choosing-the-right-face-mask-for-an-image-pipeline", "markdown": "https://wpnews.pro/news/bisenet-or-xseg-choosing-the-right-face-mask-for-an-image-pipeline.md", "text": "https://wpnews.pro/news/bisenet-or-xseg-choosing-the-right-face-mask-for-an-image-pipeline.txt", "jsonld": "https://wpnews.pro/news/bisenet-or-xseg-choosing-the-right-face-mask-for-an-image-pipeline.jsonld"}}