在这里插入图片描述

OpenCV 直方图均衡化

在图像处理中,直方图均衡化是一种强大的对比度增强技术,它通过调整图像的灰度分布,让像素值更均匀地分布在 0~255 全范围,从而显著提升图像的细节和清晰度。

本文将从原理到实战,带你深入理解并实现基于 OpenCV cv::equalizeHist 的直方图均衡化功能。


什么是直方图均衡化?

上一节我们介绍了直方图拉伸,它通过线性映射扩展图像的灰度范围。

但很多时候,图像的问题并非灰度范围狭窄,而是像素分布不均匀——比如中等灰度像素过多,而亮部和暗部像素稀少,导致图像灰蒙蒙、细节不清晰。

直方图均衡化的核心思想,就是让图像的直方图尽可能“平坦”,让所有灰度级都得到充分利用:

  • 灰度级少的像素(暗部/亮部)会被“拉高”,扩大其分布范围
  • 灰度级多的像素(中间调)会被“压缩”,降低其分布密度
  • 最终实现像素分布的均衡化,图像细节和对比度显著提升

OpenCV 核心函数:cv::equalizeHist

OpenCV 提供了现成的 cv::equalizeHist 函数,可一键实现灰度图像的直方图均衡化:

void equalizeHist(InputArray src, OutputArray dst);
  • src:输入的单通道灰度图像(必须是 8 位 CV_8UC1 类型)
  • dst:输出的均衡化图像(与输入图像尺寸、类型相同)

实现原理:从直方图到均衡化映射

直方图均衡化的本质,是基于累积直方图构建查找表(LUT),实现像素值的非线性映射。

1. 累积直方图

累积直方图表示“强度值小于或等于 i 的像素数量”,公式为:
p[i]=∑k=0ih[k] p[i] = \sum_{k=0}^{i} h[k] p[i]=k=0∑i​h[k]
其中 h[k] 是原始直方图中灰度级 k 的像素数量。

2. 均衡化映射公式

利用累积直方图构建查找表,将原始灰度值映射到新的灰度值:

lookup.at<uchar>(i) = static_cast<uchar>(255.0 * p[i] / image.total());
  • p[i]:累积直方图中灰度级 i 对应的像素总数
  • image.total():图像的总像素数
  • 255.0 * p[i] / image.total():将累积百分比映射到 0~255 范围

Android Native JNI 完整实现

1. Native 层 native-lib.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);

    AndroidBitmap_unlockPixels(env, bitmap);
    return bgr;
}

void matToBitmap(JNIEnv *env, const Mat &mat, jobject bitmap) {
    AndroidBitmapInfo info;
    void *pixels;
    AndroidBitmap_getInfo(env, bitmap, &info);
    AndroidBitmap_lockPixels(env, bitmap, &pixels);

    Mat rgba;
    cvtColor(mat, rgba, COLOR_BGR2RGBA);
    memcpy(pixels, rgba.data, info.height * info.width * 4);

    AndroidBitmap_unlockPixels(env, bitmap);
}

// ==========================
// 灰度直方图均衡化
// ==========================
Mat equalizeImage(const Mat &gray) {
    Mat result;
    equalizeHist(gray, result);
    return result;
}

// ==========================
// 你提供的:彩色直方图均衡化
// ==========================
Mat equalizeColorImage(const Mat &src) {
    Mat ycrcb;
    cvtColor(src, ycrcb, COLOR_BGR2YCrCb);
    vector<Mat> channels;
    split(ycrcb, channels);

    // 仅对亮度通道均衡化
    equalizeHist(channels[0], channels[0]);

    merge(channels, ycrcb);
    Mat result;
    cvtColor(ycrcb, result, COLOR_YCrCb2BGR);
    return result;
}

// ==========================
// JNI 接口:一次返回两张效果图
// ==========================
extern "C" JNIEXPORT void JNICALL
Java_com_example_equalize_MainActivity_processEqualize(
        JNIEnv *env,
        jobject thiz,
        jobject srcBitmap,
        jobject outGrayBitmap,
        jobject outColorBitmap
) {
    Mat src = bitmapToMat(env, srcBitmap);

    // 1. 转灰度 + 均衡
    Mat gray;
    cvtColor(src, gray, COLOR_BGR2GRAY);
    Mat eqGray = equalizeImage(gray);

    // 2. 彩色均衡
    Mat eqColor = equalizeColorImage(src);

    // 回传
    matToBitmap(env, eqGray, outGrayBitmap);
    matToBitmap(env, eqColor, outColorBitmap);
}

2. Kotlin 层 MainActivity.kt

package com.example.equalize

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")
        }
    }

    private external fun processEqualize(
        src: Bitmap,
        outGray: Bitmap,
        outColor: Bitmap
    )

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // 原图
        val src = BitmapFactory.decodeResource(resources, R.drawable.test)

        // 创建输出位图
        val equalizedGray = Bitmap.createBitmap(src.width, src.height, Bitmap.Config.ARGB_8888)
        val equalizedColor = Bitmap.createBitmap(src.width, src.height, Bitmap.Config.ARGB_8888)

        // 调用 C++
        processEqualize(src, equalizedGray, equalizedColor)

        // 显示
        findViewById<ImageView>(R.id.iv_original).setImageBitmap(src)
        findViewById<ImageView>(R.id.iv_equalized_gray).setImageBitmap(equalizedGray)
        findViewById<ImageView>(R.id.iv_equalized_color).setImageBitmap(equalizedColor)
    }
}

3. 布局文件 activity_main.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="horizontal"
    android:weightSum="3">

    <!-- 原图 -->
    <ImageView
        android:id="@+id/iv_original"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:scaleType="centerCrop"/>

    <!-- 灰度均衡化 -->
    <ImageView
        android:id="@+id/iv_equalized_gray"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:scaleType="centerCrop"/>

    <!-- 彩色均衡化 -->
    <ImageView
        android:id="@+id/iv_equalized_color"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:scaleType="centerCrop"/>

</LinearLayout>

在这里插入图片描述


效果说明

  • 原图:可能存在对比度不足、细节模糊的问题,像素分布集中在中间调。
  • 均衡化图像:像素分布更均匀,暗部细节和亮部细节都得到了增强,图像整体对比度显著提升。

直方图均衡化实现原理

直方图均衡化通过以下步骤实现图像增强:

  1. 计算原始直方图:统计每个灰度级的像素数量。
  2. 计算累积直方图:得到每个灰度级的累积像素数。
  3. 构建均衡化查找表:将累积百分比映射到 0~255 范围,生成新的像素映射关系。
  4. 应用映射:使用查找表对图像进行像素重映射,实现直方图均衡化。

与直方图拉伸相比,直方图均衡化是一种非线性增强方法,能更有效地处理像素分布不均匀的图像,提升细节表现。


总结

直方图均衡化是一种高效的图像增强技术,通过调整像素分布,让图像对比度更均匀、细节更清晰。本文从原理到实战,完整实现了:

  1. 灰度图像直方图均衡化
  2. Android Native JNI 完整移植方案
  3. 彩色图像均衡化扩展

在这里插入图片描述

更多推荐