在Android上玩转Opencv 系列:18.基础知识 图像帧差与背景建模

210 阅读3分钟

Android 版本 OpenCV 图像侦差法(Image Difference Detection)的使用

图像侦差法(Image Difference)主要用于运动检测、变化检测、物体检测等。它通过计算两幅图像之间的像素差异,识别发生变化的区域。OpenCV 提供了多种方法来实现图像侦差。

1. 主要方法

(1) 直接像素差分法

• 适用于两张静态图像对比,计算像素级差异。

• 使用 Core.absdiff() 计算两张图像的绝对差分

• 适用于静态背景和变化区域明显的情况

(2) 帧间差分法

• 适用于视频流,通过相邻帧比较检测运动物体。

• 适用于摄像头监控、运动检测等应用。

(3) 背景建模(Background Subtraction)

• 适用于动态场景,用于去除背景,仅保留前景运动目标。

• OpenCV 提供了 BackgroundSubtractorMOG2 和 BackgroundSubtractorKNN 方法。

2. 代码实现

方法1:直接像素差分法

适用于两张静态图像的对比。

实现步骤

  1. 读取两张图像,并转换为灰度图。

  2. 使用 Core.absdiff() 计算像素级差异。

  3. 进行二值化处理,增强对比度。

  4. 找到差异区域并进行轮廓检测。

import org.opencv.android.Utils;
import org.opencv.core.*;
import org.opencv.imgproc.Imgproc;
import android.graphics.Bitmap;

public class ImageDifference {

    public static Bitmap detectDifference(Bitmap img1, Bitmap img2) {
        // 转换 Bitmap 为 Mat
        Mat mat1 = new Mat();
        Mat mat2 = new Mat();
        Utils.bitmapToMat(img1, mat1);
        Utils.bitmapToMat(img2, mat2);

        // 转换为灰度图
        Imgproc.cvtColor(mat1, mat1, Imgproc.COLOR_BGR2GRAY);
        Imgproc.cvtColor(mat2, mat2, Imgproc.COLOR_BGR2GRAY);

        // 计算绝对差分
        Mat diff = new Mat();
        Core.absdiff(mat1, mat2, diff);

        // 进行二值化处理,增强差异区域
        Imgproc.threshold(diff, diff, 25, 255, Imgproc.THRESH_BINARY);

        // 轮廓检测
        Mat hierarchy = new Mat();
        List<MatOfPoint> contours = new ArrayList<>();
        Imgproc.findContours(diff, contours, hierarchy, Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE);

        // 绘制轮廓
        for (int i = 0; i < contours.size(); i++) {
            Imgproc.drawContours(mat1, contours, i, new Scalar(0, 255, 0), 2);
        }

        // 转换回 Bitmap
        Bitmap resultBitmap = Bitmap.createBitmap(mat1.cols(), mat1.rows(), Bitmap.Config.ARGB_8888);
        Utils.matToBitmap(mat1, resultBitmap);

        return resultBitmap;
    }
}

方法2:帧间差分法(适用于视频)

用于检测视频中的运动物体。

实现步骤

  1. 从摄像头获取连续帧。

  2. 计算当前帧和上一帧之间的差异。

  3. 进行阈值化处理,突出变化区域。

  4. 通过形态学操作(如膨胀)减少噪声。

import org.opencv.core.*;
import org.opencv.imgproc.Imgproc;
import org.opencv.videoio.VideoCapture;

public class MotionDetection {
    private Mat previousFrame = new Mat();

    public Mat detectMotion(Mat currentFrame) {
        Mat gray = new Mat();
        Imgproc.cvtColor(currentFrame, gray, Imgproc.COLOR_BGR2GRAY);

        if (previousFrame.empty()) {
            gray.copyTo(previousFrame);
            return currentFrame;
        }

        // 计算帧差
        Mat diff = new Mat();
        Core.absdiff(previousFrame, gray, diff);

        // 二值化处理
        Imgproc.threshold(diff, diff, 30, 255, Imgproc.THRESH_BINARY);

        // 形态学操作,去噪
        Imgproc.dilate(diff, diff, new Mat(), new Point(-1, -1), 2);

        // 轮廓检测
        Mat hierarchy = new Mat();
        List<MatOfPoint> contours = new ArrayList<>();
        Imgproc.findContours(diff, contours, hierarchy, Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE);

        // 在当前帧上绘制运动目标框
        for (MatOfPoint contour : contours) {
            Rect rect = Imgproc.boundingRect(contour);
            Imgproc.rectangle(currentFrame, rect.tl(), rect.br(), new Scalar(0, 255, 0), 2);
        }

        // 更新上一帧
        gray.copyTo(previousFrame);
        return currentFrame;
    }
}

方法3:背景建模(适用于复杂场景)

当背景不是固定的,可以使用背景减法来检测前景目标。

import org.opencv.video.BackgroundSubtractorMOG2;

public class BackgroundSubtraction {
    private BackgroundSubtractorMOG2 bgSubtractor = Video.createBackgroundSubtractorMOG2();

    public Mat detectForeground(Mat frame) {
        Mat fgMask = new Mat();
        bgSubtractor.apply(frame, fgMask);

        // 形态学操作,去噪
        Imgproc.morphologyEx(fgMask, fgMask, Imgproc.MORPH_OPEN, new Mat());

        return fgMask;
    }
}

3. 关键技术解析

(1) Core.absdiff()

计算两幅图像的像素差异:

Core.absdiff(mat1, mat2, diff);

(2) Imgproc.threshold()

对差异图像进行二值化:

Imgproc.threshold(diff, diff, 25, 255, Imgproc.THRESH_BINARY);

• 25 是阈值,表示低于 25 的像素被设为 0,高于 25 的像素设为 255。

(3) Imgproc.findContours()

检测二值图中的轮廓:

Imgproc.findContours(diff, contours, hierarchy, Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE);

(4) Imgproc.rectangle()

绘制检测到的变化区域:

Imgproc.rectangle(mat1, matchLoc, new Point(matchLoc.x + template.cols(), matchLoc.y + template.rows()), new Scalar(0, 255, 0), 2);

4. 适用场景对比

方法适用场景优点缺点
直接像素差分法静态图片对比计算简单,适用于小场景不能处理动态变化
帧间差分法监控、运动检测实时性强,计算高效需要连续帧,不适用于大范围场景
背景建模复杂环境下的目标检测适用于长期监控,能分离前景计算较复杂,背景变化大时效果不佳

总结

简单对比 → Core.absdiff()

视频运动检测 → 帧间差分

复杂场景目标检测 → 背景减法

你打算应用在哪种场景?如果是实时摄像头检测,可以用帧间差分,如果是目标跟踪,可以结合背景建模特征匹配