
GrabCut 前景提取算法
GrabCut 是 OpenCV 中基于图割(Graph Cuts)的交互式前景提取算法,通过用户简单标记(如矩形框或掩码),能自动迭代优化出物体与背景的精准分割边界,是静态图像抠图的经典方案。
本文将从原理讲解、API解析、Android完整源码实现三个维度,带你掌握这一算法的工程落地。
核心原理:迭代图割与颜色模型
1. 算法核心思想
GrabCut 基于图割算法,通过以下步骤实现前景/背景分割:
- 用户标记:用户提供初始标记(如包含前景的矩形框),矩形外像素标记为背景,矩形内像素标记为"可能前景"。
- 颜色建模:用高斯混合模型(GMM)分别对前景和背景的颜色分布建模,将像素划分为前景/背景组。
- 构建图模型:将图像像素构建为带权图,像素间的相似性(颜色、空间距离)作为边权,前景/背景标记作为终端节点。
- 图割优化:通过最小割算法找到前景与背景的最优分割边界,最小化能量函数(包含颜色相似性和边界平滑项)。
- 迭代优化:重复上述步骤,更新颜色模型和分割结果,直到收敛,最终得到精准的前景掩码。
2. 标记类型说明
OpenCV 定义了4种像素标记,用于描述像素的类别:
| 标记值 | 常量名 | 含义 |
|---|---|---|
| 0 | GC_BGD |
明确属于背景 |
| 1 | GC_FGD |
明确属于前景 |
| 2 | GC_PR_BGD |
可能属于背景 |
| 3 | GC_PR_FGD |
可能属于前景 |
OpenCV 核心 API 解析
1. GrabCut 核心函数
cpp
void grabCut(
InputArray img, // 输入图像(8位3通道BGR)
InputOutputArray mask, // 输入/输出掩码(8位单通道,包含4种标记)
Rect rect, // 包含前景的矩形框(仅在 GC_INIT_WITH_RECT 模式下有效)
InputOutputArray bgdModel, // 背景模型(算法内部使用,用户无需处理)
InputOutputArray fgdModel, // 前景模型(算法内部使用,用户无需处理)
int iterCount, // 迭代次数(通常5次足够)
int mode = GC_INIT_WITH_RECT // 初始化模式:矩形框/掩码
);
2. 关键参数说明
mask:既是输入也是输出,算法执行后会更新掩码的标记值,反映最终分割结果。rect:矩形框仅在GC_INIT_WITH_RECT模式下有效,用于快速标记初始前景区域。iterCount:迭代次数越多,分割结果越精准,但耗时也会增加,一般取 3-5 次即可。mode:支持两种初始化模式:GC_INIT_WITH_RECT:通过矩形框初始化,简单易用,适合快速分割。GC_INIT_WITH_MASK:通过用户自定义掩码初始化,支持更精细的前景/背景标记。
3. 配套辅助 API
compare:根据掩码值筛选前景像素(如筛选GC_PR_FGD标记的像素)。copyTo:根据掩码将前景像素复制到目标图像,生成抠图结果。
Android 完整项目实现
1. 布局文件:activity_main.xml
xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="8dp">
<!-- 原图 -->
<ImageView
android:id="@+id/iv_original"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:scaleType="fitCenter"
android:adjustViewBounds="true"/>
<!-- 掩码图像 -->
<ImageView
android:id="@+id/iv_mask"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:layout_marginTop="4dp"
android:scaleType="fitCenter"
android:adjustViewBounds="true"/>
<!-- 抠图结果 -->
<ImageView
android:id="@+id/iv_result"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:layout_marginTop="4dp"
android:scaleType="fitCenter"
android:adjustViewBounds="true"/>
</LinearLayout>
2. Kotlin 上层代码:MainActivity.kt
kotlin
package com.nicoli.grabcutdemo
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.os.Bundle
import android.widget.ImageView
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity() {
companion object {
init {
System.loadLibrary("native-lib")
}
}
// JNI 接口声明
private external fun grabCutSegment(src: Bitmap, outMask: Bitmap, outResult: Bitmap)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 加载原图(包含前景物体的图像)
val originalBitmap = BitmapFactory.decodeResource(resources, R.drawable.animals)
// 创建输出位图
val maskBitmap = Bitmap.createBitmap(originalBitmap.width, originalBitmap.height, Bitmap.Config.ARGB_8888)
val resultBitmap = Bitmap.createBitmap(originalBitmap.width, originalBitmap.height, Bitmap.Config.ARGB_8888)
// 执行 GrabCut 前景提取
grabCutSegment(originalBitmap, maskBitmap, resultBitmap)
// 显示结果
findViewById<ImageView>(R.id.iv_original).setImageBitmap(originalBitmap)
findViewById<ImageView>(R.id.iv_mask).setImageBitmap(maskBitmap)
findViewById<ImageView>(R.id.iv_result).setImageBitmap(resultBitmap)
}
}
3. C++ 核心算法:native-lib.cpp(逐行注释)
cpp
#include <jni.h>
#include <opencv2/opencv.hpp>
#include <android/bitmap.h>
using namespace cv;
using namespace std;
// ====================== 工具函数:Bitmap ↔ Mat 转换 ======================
Mat bitmapToMat(JNIEnv *env, jobject bitmap) {
AndroidBitmapInfo info;
void* pixels;
AndroidBitmap_getInfo(env, bitmap, &info);
AndroidBitmap_lockPixels(env, bitmap, &pixels);
Mat rgba(info.height, info.width, CV_8UC4, pixels);
Mat bgr;
cvtColor(rgba, bgr, COLOR_RGBA2BGR); // 转为BGR格式(OpenCV默认)
AndroidBitmap_unlockPixels(env, bitmap);
return bgr;
}
void matToBitmap(JNIEnv *env, const Mat& srcMat, jobject dstBitmap) {
AndroidBitmapInfo info;
void* pixels;
AndroidBitmap_getInfo(env, dstBitmap, &info);
AndroidBitmap_lockPixels(env, dstBitmap, &pixels);
Mat rgba;
if (srcMat.channels() == 1) {
cvtColor(srcMat, rgba, COLOR_GRAY2RGBA);
} else {
cvtColor(srcMat, rgba, COLOR_BGR2RGBA);
}
memcpy(pixels, rgba.data, info.width * info.height * 4);
AndroidBitmap_unlockPixels(env, dstBitmap);
}
// ====================== GrabCut 前景提取核心函数 ======================
void grabCutForeground(const Mat& srcBgr, Mat& outMask, Mat& outResult) {
// 1. 定义包含前景的矩形框(根据图像内容调整)
Rect rect(50, 70, srcBgr.cols - 100, srcBgr.rows - 100);
// 2. 初始化掩码和模型
Mat mask(srcBgr.size(), CV_8UC1, Scalar(GC_BGD));
mask(rect).setTo(GC_PR_FGD); // 矩形内标记为可能前景
Mat bgModel, fgdModel;
// 3. 执行 GrabCut 算法
grabCut(srcBgr, mask, rect, bgModel, fgdModel, 5, GC_INIT_WITH_RECT);
// 4. 提取前景掩码(GC_FGD 或 GC_PR_FGD)
Mat foregroundMask;
compare(mask, GC_PR_FGD, foregroundMask, CMP_EQ);
foregroundMask |= (mask == GC_FGD);
// 5. 生成抠图结果(白色背景)
Mat foreground(srcBgr.size(), CV_8UC3, Scalar(255, 255, 255));
srcBgr.copyTo(foreground, foregroundMask);
// 6. 转换掩码为可视化灰度图
outMask = mask * 85; // 标记值0→0,1→85,2→170,3→255,方便显示
outResult = foreground;
}
// ====================== JNI 接口 ======================
extern "C" JNIEXPORT void JNICALL
Java_com_nicoli_grabcutdemo_MainActivity_grabCutSegment
(JNIEnv *env, jobject thiz, jobject srcBitmap, jobject outMask, jobject outResult) {
// 1. 转换Bitmap为OpenCV Mat
Mat srcBgr = bitmapToMat(env, srcBitmap);
// 2. 执行GrabCut前景提取
Mat maskMat, resultMat;
grabCutForeground(srcBgr, maskMat, resultMat);
// 3. 转换结果回Bitmap
matToBitmap(env, maskMat, outMask);
matToBitmap(env, resultMat, outResult);
}

效果与参数详解
1. 运行效果
- 原图:包含前景物体和背景的彩色图像。
- 掩码图像:不同灰度值表示像素标记,黑色为背景、白色为前景,中间灰度为不确定区域。
- 抠图结果:前景物体被完整提取,背景替换为白色,边界清晰自然。
2. 关键参数说明
- 矩形框位置:必须完全包含前景物体,且尽量靠近物体边缘,避免过多背景像素影响分割效果。
- 迭代次数:一般取 3-5 次即可,对于复杂场景(如前景背景颜色相似),可增加到 8-10 次。
- 掩码模式 :如果需要更精细的控制,可使用
GC_INIT_WITH_MASK模式,手动标记部分前景/背景像素,提升分割精度。
扩展应用与优化
1. 交互式标记优化
在实际项目中,可通过用户触摸交互,让用户在图像上绘制前景/背景标记,再调用 GrabCut 算法分割物体,实现更精准的抠图效果。
2. 后处理优化
- 边缘平滑:分割后可对掩码进行形态学开/闭运算,去除边缘毛刺,填充内部孔洞。
- 颜色校正:提取前景后,可调整前景物体的亮度、对比度,使其与新背景更融合。
3. 性能优化
- 降采样处理:对大图像先降采样再执行 GrabCut,提升处理速度,分割完成后再将掩码上采样回原图尺寸。
- ROI 裁剪:根据用户标记的矩形框,裁剪出包含前景的局部区域,仅对局部区域执行分割,减少计算量。
总结
GrabCut 算法通过用户简单标记和迭代图割优化,实现了高精度的前景/背景分割,是静态图像抠图的经典方案。
核心在于初始标记的设置,合理的矩形框或掩码标记能显著提升分割效果。在文本识别、图像编辑、目标检测等场景中,GrabCut 可作为强大的图像预处理工具,提取目标物体,去除背景干扰。
