学习 Android (十六) 学习 OpenCV (一)

在前几个章节中,我们对 NDK 相关的开发有了一定的了解,所谓磨刀不误砍柴工,有了这些基础的知识储备之后,我们可以来简单上手一下 OpenCV 相关的知识,接下来跟随作者一起来学习吧:

  • 什么是 OpenCV ?

  • 搭建 OpenCV Android SDK 环境

  • Mat 类详解


1. 什么是 OpenCV?

OpenCVOpen Source Computer Vision Library)是一个开源的计算机视觉和机器学习软件库,由英特尔(Intel)于 1999 年首次发布,现由开源社区维护。它提供了丰富的工具和算法,用于处理图像和视频数据,广泛应用于图像处理、物体检测、人脸识别、增强现实(AR)、自动驾驶、医学影像分析等领域。

1.1 OpenCV 的核心特点

  • 跨平台支持:

    • 支持 Windows、Linux、macOS、Android 和 ios。

    • 提供 C++、Python、Java 等语言的接口。

  • 丰富的功能模块:

    • 图像处理(滤波、边缘检测、色彩空间转换等)

    • 特做检测(SIFT、SURF、ORB、角色检测)

    • 目标检测(Haar 级联、YOLD、SSD)

    • 机器学习(SVM、KNN、神经网络)

    • 摄像头标定与 3D 重建

    • 视频分析(光流、背景减除)

  • 高性能优化:

    • 底层使用 C/C++ 编写、支持多线程和 GPU 加速(如 CUDA、OpenCL)。

    • 提供预训练模型(如 DNN 模块支持 TensorFlow、PyTorch 模型)。

  • 开源免费:

    • 基于 BSD 许可证,可自由用于商业和研究用途。

1.2 OpenCV 的典型应用场景

领域应用示例
人脸识别人脸检测、表情分析、活体检测(如手机解锁)
自动驾驶车道检测、交通标志识别、行人检测
医学影像肿瘤检测、X 光分析、显微镜图像处理
工业检测缺陷检测、二维码识别、物体测量
增强现实(AR)虚拟贴纸、3D 物体叠加(如 Snapchat 滤镜)
机器人SLAM(同步定位与地图构建)、避障

1.3 OpenCV 的架构

  • 核心模块(Core)

    • 基础数据结构(如 Mat 存储图像矩阵)。

    • 数学运算、文件 I/O。

  • Imgproc(图像处理)

    • 滤波(高斯模糊、中值滤波)、边缘检测(Canny)、几何变换(旋转、缩放)。
  • HighGUI(图形界面)

    • 显示图像、视频捕获、滑动条交互。
  • DNN(深度学习)

    • 支持加载 TensorFlow、PyTorch 模型进行推理。
  • Calib3d(相机标定)

    • 相机畸变校正、3D 重建。

2. 搭建 OpenCV Android SDK

在对 OpenCV 有了初步的了解之后,我们开始进行 Android 相关的 SDK 搭建,我们可以从官方下载 OpenCV Android SDK 包 Releases - OpenCV,这里作者选择了最新的 OpenCV - 4.12.0 Android 的包

在这里插入图片描述

将下载好的压缩包解压至自己的目录下,然后我们在 Android Studio 中进行操作,File -> New Project -> Native C++

在这里插入图片描述

在这里插入图片描述

至此我们创建一个了 Andorid Native 项目,因为从官方下载的 Android SDK 包中,提供给我的是 .a 静态库,而不是 .so 动态库,不过这都无所谓,对目前我们来说,只是去了解一下 OpenCV 而已,之后作者进行处理交叉编译获取到 .so 文件的,所以我现在就直接导入我们下载的文件中的 SDK 模块至项目中

在这里插入图片描述

选择下载后解压的目录至 sdk 目录下,这里作者的目录是 F:\OpenCV_SDK\opencv-4.12.0-android-sdk\OpenCV-android-sdk\sdk,读者根据自己的来替换

在这里插入图片描述

点击 FINISH,会卡顿一会,等待模块的导入即可,接下来我们将添加的 OpenCV 模块添加到我们的 app 模块中

在这里插入图片描述
在这里插入图片描述

点击ok即可,完成添加,在 MainActivity 代码中尝试是否有 opencv 相关的库

在这里插入图片描述

有就说明我们添加成功,至此,OpenCV 的 Android SDK 环境就已经搭建好了,接下来我们将了解并学习 OpenCV 的基础操作


3. Mat 类详解

在Android OpenCV SDK中,Mat类是用于存储图像数据的核心数据结构。理解 Mat 是高效使用 OpenCV 的关键。

3.1 Mat 类概述
  1. 定义:

    Mat 是一个智能指针类,用于存储 n 维的矩阵(数组),特别适合存储和处理图像数据,所以 Mat 类也用于表示图像,支持多种数据类型和通道数。它是 OpenCV 中最基本的数据结构之一。

  2. 构造函数:

    import org.opencv.core.CvType;
    import org.opencv.core.Mat;// 空构造函数
    Mat emptyMat = new Mat();// 创建指定大小的矩阵
    Mat mat480x640 = new Mat(480, 640, CvType.CV_8UC3); // 480行,640列,8位无符号3通道// 从文件加载图像
    Mat redMat = Imgcodecs.imread("path/to/image.jpg");
    
  3. 数据类型:

    Mat支持多种数据类型

    数据类型描述范围示例
    CV_8U8位无符号整数0-255CvType.CV_8UC1 (灰度图)
    CV_8S8位有符号整数-128-127很少使用
    CV_16U16位无符号整数0-65535深度图像
    CV_16S16位有符号整数-32768-32767梯度图像
    CV_32S32位有符号整数标签图
    CV_32F32位浮点数处理中间结果
    CV_64F64位浮点数高精度计算
  4. 通道数:

    Mat可以有不同的通道数,例如:

    • CV_8UC1 单通道:灰度图像
    • CV_8UC3 三通道:彩色图像(如BGR)
    • CV_8UC4 四通道:带Alpha通道的图像(如BGRA)
3.2 Mat 属性和方法
  1. 基本属性

    Mat image = new Mat(480, 640, CvType.CV_8UC3);int rows = image.rows();     // 480
    int cols = image.cols();     // 640
    int channels = image.channels(); // 3
    int depth = image.depth();   // CvType.CV_8U
    int type = image.type();     // CvType.CV_8UC3
    long total = image.total();  // 480 * 640 = 307200
    boolean empty = image.empty(); // 是否为空
    
  2. 方法

    方法名描述
    Mat() / Mat(rows, cols, type)不同构造函数,用于创建空 Mat 或指定大小和类型的矩阵
    clone()深拷贝 Mat,包括数据内容
    copyTo(Mat dst) / copyTo(Mat dst, Mat mask)将 Mat 数据拷贝到另一个 Mat 或指定掩码区域
    convertTo(Mat m, int rtype, double alpha=..., double beta=...)数据类型转换,并可进行线性变换
    create(rows, cols, type) / create(Size size, type)重新分配 Mat 内存,如果已有空间可复用
    release()释放 Mat 内部数据(引用计数机制)
    empty()判断 Mat 是否为空(未分配数据)
    rows() / cols() / channels() / type() / depth()获取 Mat 的基本属性,如大小、通道数、类型、深度等
    total()Mat 中元素总数(行×列)
    size() / size(int i)获取 Mat 的尺寸信息(宽度、高度或多维信息)
    elemSize() / elemSize1()返回每个元素占用的字节数(全部通道或单通道)
    step1() / step1(int i)获取按元素计算的步长(列间隔)
    setTo(Scalar value, Mat mask)设置所有或掩码部分的像素值
    get(...) / put(...)获取或写入指定像素或通道数据(支持多种数据类型)
    reshape(int cn, int rows)改变 Mat 的维度或通道数,不复制数据
    row(int y) / col(int x) / rowRange(...) / colRange(...) / submat(...)获取子矩阵或区域,支持 ROI 操作
    diag(int d) / diag()获取主对角线或指定对角元素生成的矩阵
    mul(Mat m, double scale=1) / dot(Mat m) / cross(Mat m)元素乘法、点积、叉积等矩阵运算
    eye(rows, cols, type) / ones(...) / zeros(...)创建特殊矩阵:单位、全一、全零
    push_back(...)在 Mat 底部追加元素或另一个 Mat
    reshape(...)更改通道或形状(与 reshape 同)
    dump() / toString()文本形式输出 Mat 内容(调试专用)
    getNativeObjAddr()获取本地 Mat 对象地址,用于 JNI 互操作
  3. Mat 运算和算术运算实现

    • 矩阵加法

      通常的矩阵加法被定义在两个相同大小的矩阵。两个m×n矩阵A和B的和,标记为A+B,一样是个m×n矩阵,其内的各元素为其相对应元素相加后的值。例如:

      在这里插入图片描述

    • 矩阵减法

      矩阵的减法,只要其大小相同的话。A-B内的各元素为其相对应元素相减后的值,且此矩阵会和A、B有相同大小。例如:

在这里插入图片描述

  • 矩阵乘法

    1. 当矩阵A的列数(column)等于矩阵B的行数(row)时,A与B可以相乘。

    2. 矩阵C的行数等于矩阵A的行数,C的列数等于B的列数。

    3. 乘积C的第m行第n列的元素等于矩阵A的第m行的元素与矩阵B的第n列对应元素乘积之和。

    在这里插入图片描述

示例

public class MainActivity extends AppCompatActivity {private ActivityMainBinding mBinding; // 视图绑定对象,用于访问布局中的控件private Mat bgr = new Mat();   // 存储BGR格式的Mat对象private Mat source = new Mat(); // 存储RGB格式的Mat对象static {System.loadLibrary("opencv_java4"); // 加载OpenCV的Java库}@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);mBinding = ActivityMainBinding.inflate(getLayoutInflater()); // 初始化视图绑定setContentView(mBinding.getRoot()); // 设置布局try {// 从资源文件中加载lena图片到bgr Mat对象(默认BGR格式)bgr = Utils.loadResource(this, R.drawable.lena);// 将图像转换为RGB图像,并保存到sourceImgproc.cvtColor(bgr, source, Imgproc.COLOR_BGR2RGB);// 在ivSource控件中显示原始图片(资源文件)mBinding.ivSource.setImageResource(R.drawable.lena);// 创建一个与source相同尺寸的Bitmap,用于显示MatBitmap bitmap = Bitmap.createBitmap(source.width(), source.height(), Bitmap.Config.ARGB_8888);// 将bgr Mat对象转换为BitmapUtils.matToBitmap(bgr, bitmap);// 在ivBgr控件中显示转换后的BitmapmBinding.ivBgr.setImageBitmap(bitmap);} catch (IOException e) {throw new RuntimeException(e); // 图片加载失败时抛出运行时异常}// 为各个按钮绑定点击事件,调用对应的图像处理方法mBinding.bitwiseNot.setOnClickListener(view -> bitwiseNot(source));mBinding.bitwiseAnd.setOnClickListener(view -> bitwiseAnd(source, bgr));mBinding.bitwiseOr.setOnClickListener(view -> bitwiseOr(source, bgr));mBinding.bitwiseXor.setOnClickListener(view -> bitwiseXor(source, bgr));mBinding.add.setOnClickListener(view -> add(source, bgr));mBinding.subtract.setOnClickListener(view -> subtract(source, bgr));mBinding.multiply.setOnClickListener(view -> multiply(source, bgr));mBinding.divide.setOnClickListener(view -> divide(source, bgr));}/*** 将处理结果Mat转换为Bitmap并显示到ivResult*/private void showResult(Mat dst) {Bitmap bitmap = Bitmap.createBitmap(dst.width(), dst.height(), Bitmap.Config.ARGB_8888);Utils.matToBitmap(dst, bitmap);mBinding.ivResult.setImageBitmap(bitmap);}/*** 按位取反操作*/private void bitwiseNot(Mat source) {Mat dst = new Mat();Core.bitwise_not(source, dst);showResult(dst);dst.release(); // 释放内存}/*** 按位与操作*/private void bitwiseAnd(Mat source, Mat attach) {Mat dst = new Mat();Core.bitwise_and(source, attach, dst);showResult(dst);dst.release();}/*** 按位或操作*/private void bitwiseOr(Mat source, Mat attach) {Mat dst = new Mat();Core.bitwise_or(source, attach, dst);showResult(dst);dst.release();}/*** 按位异或操作*/private void bitwiseXor(Mat source, Mat attach) {Mat dst = new Mat();Core.bitwise_xor(source, attach, dst);showResult(dst);dst.release();}/*** 图像加法*/private void add(Mat source, Mat attach) {Mat dst = new Mat();Core.add(source, attach, dst);showResult(dst);dst.release();}/*** 图像减法*/private void subtract(Mat source, Mat attach) {Mat dst = new Mat();Core.subtract(source, attach, dst);showResult(dst);dst.release();}/*** 图像乘法*/private void multiply(Mat source, Mat attach) {Mat dst = new Mat();Core.multiply(source, attach, dst);showResult(dst);dst.release();}/*** 图像除法(额外缩放系数为50.0,通道不变)*/private void divide(Mat source, Mat attach) {Mat dst = new Mat();Core.divide(source, attach, dst, 50.0, -1); // 参数:src1, src2, dst, scale, dtypeCore.convertScaleAbs(dst, dst); // 转换为8位无符号整型并取绝对值showResult(dst);dst.release();}}

原图与 Mat 默认处理图

在这里插入图片描述

按位非

在这里插入图片描述

按位与
在这里插入图片描述

按位或

在这里插入图片描述

按位异或

在这里插入图片描述

矩阵加法

在这里插入图片描述

矩阵减法

在这里插入图片描述

矩阵乘法

在这里插入图片描述

矩阵除法
在这里插入图片描述

4. Imgproc 类详解

在Android OpenCV中,Imgproc类是一个非常重要的工具,专门用于图像处理。它提供了多种功能,包括图像转换、滤波、边缘检测、形态学操作等。以下是对Imgproc类的详细讲解:

4.1 模块概述

Imgproc(Image Processing Module)模块包含:

  • 图像滤波与平滑

    // 高斯模糊
    Imgproc.GaussianBlur(src, dst, new Size(5, 5), 0);// 中值滤波
    Imgproc.medianBlur(src, dst, 5);// 双边滤波(保边去噪)
    Imgproc.bilateralFilter(src, dst, 9, 75, 75);// 自定义卷积核
    Mat kernel = Imgproc.getGaussianKernel(5, 1.5);
    Imgproc.filter2D(src, dst, -1, kernel);
    

在这里插入图片描述

  • 几何变换

    // 图像缩放
    Imgproc.resize(src, dst, new Size(width*0.5, height*0.5));// 图像旋转
    Mat rotMat = Imgproc.getRotationMatrix2D(new Point(width/2, height/2), 45, 1.0);
    Imgproc.warpAffine(src, dst, rotMat, src.size());// 透视变换
    Mat perspectiveMat = Imgproc.getPerspectiveTransform(srcPoints, dstPoints);
    Imgproc.warpPerspective(src, dst, perspectiveMat, src.size());
    

在这里插入图片描述

  • 形态学操作

    // 创建结构元素
    Mat kernel = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(5,5));// 膨胀
    Imgproc.dilate(src, dst, kernel);// 腐蚀
    Imgproc.erode(src, dst, kernel);// 开运算(先腐蚀后膨胀)
    Imgproc.morphologyEx(src, dst, Imgproc.MORPH_OPEN, kernel);// 闭运算(先膨胀后腐蚀)
    Imgproc.morphologyEx(src, dst, Imgproc.MORPH_CLOSE, kernel);
    

    在这里插入图片描述

  • 阈值处理

    // 简单阈值
    Imgproc.threshold(src, dst, 127, 255, Imgproc.THRESH_BINARY);// 自适应阈值
    Imgproc.adaptiveThreshold(src, dst, 255, Imgproc.ADAPTIVE_THRESH_GAUSSIAN_C, Imgproc.THRESH_BINARY, 11, 2);// Otsu阈值
    Imgproc.threshold(src, dst, 0, 255, Imgproc.THRESH_BINARY | Imgproc.THRESH_OTSU);
    

在这里插入图片描述

  • 边缘检测

    // Canny边缘检测
    Imgproc.Canny(src, edges, 50, 150);
    55
    // Sobel算子
    Mat gradX = new Mat();
    Imgproc.Sobel(src, gradX, CvType.CV_16S, 1, 0, 3);
    Core.convertScaleAbs(gradX, gradX);// Laplacian算子
    Mat laplacian = new Mat();
    Imgproc.Laplacian(src, laplacian, CvType.CV_16S, 3);
    Core.convertScaleAbs(laplacian, laplacian);
    

    在这里插入图片描述

  • 轮廓分析

    List<MatOfPoint> contours = new ArrayList<>();
    Mat hierarchy = new Mat();// 查找轮廓
    Imgproc.findContours(binaryImage, contours, hierarchy, Imgproc.RETR_TREE, Imgproc.CHAIN_APPROX_SIMPLE);// 绘制轮廓
    Mat drawing = Mat.zeros(binaryImage.size(), CvType.CV_8UC3);
    for (int i = 0; i < contours.size(); i++) {Scalar color = new Scalar(0, 255, 0);Imgproc.drawContours(drawing, contours, i, color, 2);
    }// 轮廓近似
    MatOfPoint2f approxCurve = new MatOfPoint2f();
    for (MatOfPoint contour : contours) {MatOfPoint2f contour2f = new MatOfPoint2f(contour.toArray());double epsilon = 0.02 * Imgproc.arcLength(contour2f, true);Imgproc.approxPolyDP(contour2f, approxCurve, epsilon, true);
    }
    

在这里插入图片描述

  • 直方图处理

    // 计算直方图
    List<Mat> images = new ArrayList<>();
    images.add(src);
    MatOfInt channels = new MatOfInt(0); // 通道0
    Mat hist = new Mat();
    MatOfInt histSize = new MatOfInt(256);
    MatOfFloat ranges = new MatOfFloat(0, 256);
    Imgproc.calcHist(images, channels, new Mat(), hist, histSize, ranges);// 直方图均衡化
    Mat gray = new Mat();
    Imgproc.cvtColor(src, gray, Imgproc.COLOR_BGR2GRAY);
    Mat equalized = new Mat();
    Imgproc.equalizeHist(gray, equalized);
    

在这里插入图片描述

  • 霍夫变换

    // 霍夫直线检测
    Mat lines = new Mat();
    Imgproc.HoughLinesP(edges, lines, 1, Math.PI/180, 50, 50, 10);// 绘制检测到的直线
    for (int i = 0; i < lines.rows(); i++) {double[] data = lines.get(i, 0);double x1 = data[0], y1 = data[1], x2 = data[2], y2 = data[3];Imgproc.line(dst, new Point(x1, y1), new Point(x2, y2), new Scalar(0, 0, 255), 3);
    }// 霍夫圆检测
    Mat circles = new Mat();
    Imgproc.HoughCircles(edges, circles, Imgproc.HOUGH_GRADIENT, 1, 30, 100, 30, 10, 100);
    

    在这里插入图片描述

  • 图像金字塔等

    // 高斯金字塔降采样
    Mat down = new Mat();
    Imgproc.pyrDown(src, down);// 高斯金字塔上采样
    Mat up = new Mat();
    Imgproc.pyrUp(down, up);// 拉普拉斯金字塔
    Mat downUp = new Mat();
    Imgproc.pyrUp(down, downUp);
    Mat laplacian = new Mat();
    Core.subtract(src, downUp, laplacian);
    

在这里插入图片描述

Imgproc类是进行图像处理的核心工具,提供了丰富的功能来进行图像转换、滤波、边缘检测、形态学操作等。掌握这些方法将帮助我们在Android OpenCV中高效处理图像数据。由于篇幅问题,这里不进行全部模块的详细讲解,这里就讲解一下各个模块的简单的示例,在之后的文章中我们在分别对模块进行单独的了解。

5. 亮度与对比度

亮度和对比度是图像处理和视觉表现中两个重要的概念。它们直接影响图像的质量和视觉效果。以下是对这两个概念的详细介绍。

5.1 亮度

5.1.1 定义

亮度是指图像中光的强度或明亮程度。它影响到图像的整体明暗程度。

5.1.2 影响因素
  • 光源:光源的类型和强度会直接影响亮度

  • 图像内容:图像中的颜色和纹理对亮度感知也有影响

5.1.3 调整亮度
  • 增加亮度:使图像看起来更亮,可能会导致细节丢失,尤其是在高光区域。

  • 降低亮度:使图像变暗,可能会导致细节丢失,尤其是在阴影区域。

5.1.4 应用场景
  • 在照片编辑软件中,亮度调整通常用于改善图像的可视性和清晰度。

  • 在视频制作中,亮度条左右帮助创造不同的氛围和情绪。

5.1.5 数学公式
g(x,y)=f(x,y)+β
  • f(x,y):原始图像像素值

  • g(x,y):输出图像像素值

  • β:亮度调节参数(偏移量),可正可负

5.2 对比度

5.2.1 定义

对比度是指图像中最亮和最暗部分之间的差异。高对比度意味图像中有很强的明暗对比,而低对比度则表现为图像的亮度差异较小。

5.2.2 影响因素
  • 色彩:鲜艳的颜色通常会增加对比度,而灰色或相似色的组合会降低对比度。

  • 光照条件:光照的变化也会影响对比度的感知。

5.2.3 调整对比度
  • 增加对比度:使亮部更亮、暗部更暗,增强图像的清晰度和层次感。

  • 降低对比度:使图像亮度差异减少,图像看起来更加平坦,适合某些艺术效果。

5.2.4 应用场景
  • 在摄影和图像处理中,通过调整对比度可以突出主要对象,使其更具吸引力。

  • 在设计中,高对比度可以用于吸引注意力,而低对比度则适合柔和的视觉效果。

5.2.5 数学公式
g(x,y)=α⋅f(x,y)

α:对比度参数(缩放系数),一般 >0

  • α>1:对比度增强

  • 0<α<1:对比度降低

5.3 亮度与对比度的关系

  • 相辅相成:亮度和对比度往往是相互影响的。调整亮度可能会影响对比度,反之亦然。

  • 视觉效果:适当的亮度和对比度调整可以极大改善图像的视觉效果,增加细节的可见性。

  • 结合公式:在 OpenCV 中,通常同时调节亮度和对比度:

    g(x,y)=α⋅f(x,y)+β

    • α:对比度控制(缩放因子)

    • β:亮度控制(偏移量)

5.4 示例

这里我是实现一个滑动调整图片亮度和对比度的示例 Demo,我们知道,同时调节亮度和对比度的公式实际上是 对比度控制(缩放因子)先和原始图像像素值相乘,然后再加上亮度控制(偏移量)

布局代码就不是提供了,直接上代码

public class ContrastBrightnessActivity extends AppCompatActivity {// 视图绑定private ActivityContrastBrightnessBinding mBinding;// 加载 OpenCV 本地库static {System.loadLibrary("opencv_java4");}// 存储图像的 Mat 对象private Mat source;// 存储原始亮度、当前亮度和对比度的变量private double originBrightness = 0.0;private double brightness = 0.0;private double contrast = 100.0;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);// 使用视图绑定来膨胀布局mBinding = ActivityContrastBrightnessBinding.inflate(getLayoutInflater());setContentView(mBinding.getRoot());try {// 从资源中加载源图像Mat bgr = Utils.loadResource(this, R.drawable.lena);// 将图像转换为灰度 Matsource = new Mat();Imgproc.cvtColor(bgr, source, Imgproc.COLOR_BGR2RGB);// 从 Mat 创建 Bitmap,并设置到 ImageViewBitmap bitmap = Bitmap.createBitmap(source.width(), source.height(), Bitmap.Config.ARGB_8888);Utils.matToBitmap(source, bitmap);mBinding.ivSource.setImageBitmap(bitmap);// 计算图像的原始亮度originBrightness = Core.mean(source).val[0];mBinding.sbBrightness.setProgress((int) originBrightness);// 设置亮度 SeekBar 的监听器mBinding.sbBrightness.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {@Overridepublic void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {// 更新亮度值并调整图像brightness = progress;adjustBrightnessContrast();}@Overridepublic void onStartTrackingTouch(SeekBar seekBar) {// 开始滑动时无需操作}@Overridepublic void onStopTrackingTouch(SeekBar seekBar) {// 停止滑动时无需操作}});} catch (IOException e) {// 处理加载资源时可能出现的 IOExceptionthrow new RuntimeException(e);}// 初始化对比度 SeekBarmBinding.sbContrast.setProgress(100);mBinding.sbContrast.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {@Overridepublic void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {// 更新对比度值并调整图像contrast = progress;adjustBrightnessContrast();}@Overridepublic void onStartTrackingTouch(SeekBar seekBar) {// 开始滑动时无需操作}@Overridepublic void onStopTrackingTouch(SeekBar seekBar) {// 停止滑动时无需操作}});}@Overrideprotected void onDestroy() {// 释放 Mat 资源以防止内存泄漏source.release();super.onDestroy();}private void adjustBrightnessContrast() {// 创建一个新的 Mat 用于最终输出Mat dst = new Mat();source.convertTo(dst, -1, contrast / 100, brightness - originBrightness);// 从最终 Mat 创建 Bitmap 并设置到 ImageViewBitmap bitmap = Bitmap.createBitmap(dst.cols(), dst.rows(), Bitmap.Config.ARGB_8888);Utils.matToBitmap(dst, bitmap);mBinding.ivSource.setImageBitmap(bitmap);// 释放中间的 Mat 以释放内存dst.release();}
}

在这里插入图片描述

6. 颜色模型及转换

6.1 基础总览

  • OpenCV 默认是 BGR (不是 RGB)Utils.loadResource/Imgcodecs.imread 得到的 3 通道图一般是 CV_8UC3(B,G,R),带透明通道是 BGRA

  • 常用 API

    • 颜色空间转换:Imgproc.cvtColor(src, dst, code)

    • 拆分/合并通道:Core.split(mat, mats) / Core.merge(mats, mat)

    • 通道重排:Core.mixChannels(srcMats, dstMats, fromTo)

    • 区间阈值(颜色分割):Core.inRange(hsv, lower, upper, mask)

  • 数据类型与范围(8 位 vs 浮点)

    • CV_8U:每通道 0–255

    • CV_32F/64F:通常归一化到 0–1(部分颜色空间如 HSV 的 H 通道会用角度制 0–360)

6.2 常用颜色空间与范围

6.2.1 BGR / RGB / BGRA
  • 含义:设备/显示友好的颜色空间。OpenCV 默认用 BGR

  • 范围(8位):B,G,R ∈ [0,255](BGRA 多一个 A∈[0,255])

  • 常用转换

    • COLOR_BGR2RGB, COLOR_BGR2GRAY, COLOR_BGRA2BGR, COLOR_BGR2HSV, …
6.2.2 GRAY (灰度)
  • 含义:亮度通道(近似人眼明暗感知)。

  • 范围(8位):Y ∈ [0,255]

  • 用途:阈值、边缘、角点等传统 CV 算法的前置处理。
    Imgproc.cvtColor(src, gray, COLOR_BGR2GRAY)

6.2.3 HSV / HLS
  • 含义:把颜色拆成色调(H)、饱和度(S) 和 亮度/明度(V/L)。对光照变化更稳。

  • 范围

    • 8位 HSV/HLS:H ∈ [0,179](不是 360!),S,V/L ∈ [0,255]

    • 浮点 HSV/HLS:H ∈ [0,360],S,V/L ∈ [0,1]

  • 用途:颜色分割(如检测红色/绿色物体)、颜色跟踪。

  • 转换COLOR_BGR2HSV, COLOR_BGR2HLS

6.2.4 YCrCb (YCbCr)
  • 含义Y 为亮度(luma),Cr/Cb 为色度(红差/蓝差)。图像/视频压缩常用。

  • 范围(8位):Y,Cr,Cb 一般按 0–255 表示(中性色度 ≈ 128)。
    (JPEG/视频存储可能使用“缩放范围”,但在 OpenCV 中通常是全幅 0–255 表示)

  • 用途:肤色检测、受光照影响较小的颜色阈值。

  • 转换COLOR_BGR2YCrCb

6.2.5 YUV (以及相机 NV21 / I420 / YV12 等)
  • 含义:Y + UV 色度,移动端相机常见 YUV420 采样(NV21 等)。

  • 转换(示例):

    • COLOR_YUV2BGR_NV21, COLOR_YUV2RGB_NV21
  • 用途:Camera 预览帧到 BGR/RGB 的实时转换。

6.2.6 CLE Lab / Luv / XYZ
  • 含义:感知均匀的颜色空间,Lab 的 L* 接近人眼亮度感觉,a*, b* 为对手色轴。

  • 范围

    • 8位 Lab:L ∈ [0,255](OpenCV 把真实 L∈[0,100]缩放到 0–255),a,b 以 128 为中性(即 a=0→128,b*=0→128)

    • 浮点 Lab:L* ∈ [0,100],a*,b* 约在 [-127,127]

  • 用途:亮度/颜色分离增强(只在 L* 做锐化/对比度增强),颜色转移等。

  • 转换COLOR_BGR2Lab, COLOR_BGR2Luv, COLOR_BGR2XYZ(默认为 D65 白点)

6.2.7 CMYK (说明)

OpenCV 没有直接的 CMYK 图像类型;若需处理印刷流程的 CMYK/ICC,需要借助其他库(如 LittleCMS)或自写公式做近似转换。

6.2.8 Bayer / RAW (说明)

传感器马赛克数据 → cvtColor 的 Bayer 代码:COLOR_BayerBG2BGR 等。

6.2.9 速查表
颜色空间通道含义8位范围典型用途转换代码
BGRB,G,R0–255显示、绘制、通用COLOR_BGR2…
GRAYY0–255阈值、边缘COLOR_BGR2GRAY
HSVH,S,VH:0–179; S,V:0–255颜色分割/追踪COLOR_BGR2HSV
HLSH,L,SH:0–179; L,S:0–255颜色分割(亮度分离)COLOR_BGR2HLS
YCrCbY,Cr,Cb0–255(Cr/Cb≈128为中性)肤色、压缩域处理COLOR_BGR2YCrCb
LabL,a,bL:0–255; a,b:0–255(128为中性)感知均匀处理、增强COLOR_BGR2Lab
6.2.10 示例

示例 Demo 可以让我们对各个颜色空间的效果有着根据直观的认知,这里只提供实现代码,布局不提供

public class ColorTransferActivity extends AppCompatActivity {private ActivityColorTransferBinding mBinding;static {System.loadLibrary("opencv_java4");}private Mat mBgr;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);mBinding = ActivityColorTransferBinding.inflate(getLayoutInflater());setContentView(mBinding.getRoot());try {mBgr = Utils.loadResource(this, R.drawable.lena);showMat(mBgr);} catch (IOException e) {throw new RuntimeException(e);}mBinding.btnBgr.setOnClickListener(view -> {showMat(mBgr);});mBinding.btnRgb.setOnClickListener(view -> {Mat rgb = new Mat();Imgproc.cvtColor(mBgr, rgb, Imgproc.COLOR_RGB2BGR);showMat(rgb);rgb.release();});mBinding.btnYuv.setOnClickListener(view -> {Mat yuv = new Mat();Imgproc.cvtColor(mBgr, yuv, Imgproc.COLOR_BGR2YUV);showMat(yuv);yuv.release();});mBinding.btnHsv.setOnClickListener(view -> {Mat hsv = new Mat();Imgproc.cvtColor(mBgr, hsv, Imgproc.COLOR_BGR2HSV);showMat(hsv);hsv.release();});mBinding.btnLab.setOnClickListener(view -> {Mat lab = new Mat();Imgproc.cvtColor(mBgr, lab, Imgproc.COLOR_BGR2Lab);showMat(lab);lab.release();});mBinding.btnGray.setOnClickListener( view -> {Mat gray = new Mat();Imgproc.cvtColor(mBgr, gray, Imgproc.COLOR_BGR2GRAY);showMat(gray);gray.release();});}private void showMat(Mat source) {Bitmap bitmap = Bitmap.createBitmap(source.cols(), source.rows(), Bitmap.Config.ARGB_8888);Utils.matToBitmap(source, bitmap);mBinding.ivSource.setImageBitmap(bitmap);}@Overrideprotected void onDestroy() {mBgr.release();super.onDestroy();}
}

在这里插入图片描述

7. 多通道分离与合并

7.1 什么是通道(Channel)

  • 图像在 OpenCV 中通常用 Mat 表示。

  • 彩色图像往往包含多个通道(Channel),比如:

    • 灰度图像(Grayscale):只有 1 个通道,范围 [0, 255],表示亮度。

    • BGR 彩色图像:有 3 个通道,分别表示蓝色(B)绿色(G)红色(R)。

    • RGBA 彩色图像:有 4 个通道,B、G、R 加上透明通道 A

在 OpenCV (默认 BGR 顺序)中,彩色图像存储结构是 矩阵的每个像素包含多个通道值 , 为此在处理某些图像时,我们可以进行通道的分离或者合并来达到我们需要的效果。

public class ChannelSplitMergeActivity extends AppCompatActivity {private ActivityChannelSplitMergeBinding mBinding;static {System.loadLibrary("opencv_java4");}private Mat mBgr;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);mBinding = ActivityChannelSplitMergeBinding.inflate(getLayoutInflater());setContentView(mBinding.getRoot());try {mBgr = Utils.loadResource(this, R.drawable.lena);showMat(mBgr);} catch (IOException e) {throw new RuntimeException(e);}mBinding.btnBgr.setOnClickListener(view -> {Mat mBgrClone = mBgr.clone();Mat rgb = new Mat();Imgproc.cvtColor(mBgrClone, rgb, Imgproc.COLOR_BGR2RGB);showMat(rgb);rgb.release();showMat(mBgrClone);});mBinding.btnRgb.setOnClickListener(view -> {Mat mBgrClone = mBgr.clone();Mat rgb = new Mat();Imgproc.cvtColor(mBgrClone, rgb, Imgproc.COLOR_RGB2BGR);showMat(rgb);rgb.release();});mBinding.btnB.setOnClickListener(view -> {Mat mBgrClone = mBgr.clone();List<Mat> channels = new ArrayList<>();Core.split(mBgrClone, channels);Core.add(channels.get(0), new Scalar(50), channels.get(0));Mat merged = new Mat();Core.merge(channels, merged);showMat(merged);merged.release();for (Mat channel : channels) {channel.release();}});mBinding.btnG.setOnClickListener(view -> {Mat mBgrClone = mBgr.clone();List<Mat> channels = new ArrayList<>();Core.split(mBgrClone, channels);Core.add(channels.get(1), new Scalar(50), channels.get(1));Mat merged = new Mat();Core.merge(channels, merged);showMat(merged);merged.release();for (Mat channel : channels) {channel.release();}});mBinding.btnR.setOnClickListener(view -> {Mat mBgrClone = mBgr.clone();List<Mat> channels = new ArrayList<>();Core.split(mBgrClone, channels);Core.add(channels.get(2), new Scalar(50), channels.get(2));Mat merged = new Mat();Core.merge(channels, merged);showMat(merged);merged.release();for (Mat channel : channels) {channel.release();}});mBinding.btnBgZero.setOnClickListener(view -> {Mat mBgrClone = mBgr.clone();Mat zero = Mat.zeros(mBgrClone.rows(), mBgrClone.cols(), CvType.CV_8UC1);List<Mat> channels = new ArrayList<>();Core.split(mBgrClone, channels);Mat mChannelR = channels.get(2);List<Mat> list = new ArrayList<>();list.add(zero);list.add(zero);list.add(mChannelR);Mat result = new Mat();Core.merge(list, result);showMat(result);result.release();});mBinding.btnBrZero.setOnClickListener(view -> {Mat mBgrClone = mBgr.clone();Mat zero = Mat.zeros(mBgrClone.rows(), mBgrClone.cols(), CvType.CV_8UC1);List<Mat> channels = new ArrayList<>();Core.split(mBgrClone, channels);Mat mChannelG = channels.get(1);List<Mat> list = new ArrayList<>();list.add(zero);list.add(mChannelG);list.add(zero);Mat result = new Mat();Core.merge(list, result);showMat(result);result.release();});mBinding.btnGrZero.setOnClickListener(view -> {Mat mBgrClone = mBgr.clone();Mat zero = Mat.zeros(mBgrClone.rows(), mBgrClone.cols(), CvType.CV_8UC1);List<Mat> channels = new ArrayList<>();Core.split(mBgrClone, channels);Mat mChannelB = channels.get(0);List<Mat> list = new ArrayList<>();list.add(mChannelB);list.add(zero);list.add(zero);Mat result = new Mat();Core.merge(list, result);showMat(result);result.release();});mBinding.btnBrRb.setOnClickListener(view -> {Mat mBgrClone = mBgr.clone();Mat zero = Mat.zeros(mBgrClone.rows(), mBgrClone.cols(), CvType.CV_8UC1);List<Mat> channels = new ArrayList<>();Core.split(mBgrClone, channels);Mat mChannelB = channels.get(0);Mat mChannelG = channels.get(1);Mat mChannelR = channels.get(2);List<Mat> list = new ArrayList<>();list.add(mChannelB);list.add(mChannelG);list.add(mChannelR);Mat result = new Mat();Core.merge(list, result);showMat(result);result.release();});}private void showMat(Mat source) {Bitmap bitmap = Bitmap.createBitmap(source.cols(), source.rows(), Bitmap.Config.ARGB_8888);Utils.matToBitmap(source, bitmap);mBinding.ivSource.setImageBitmap(bitmap);}@Overrideprotected void onDestroy() {mBgr.release();super.onDestroy();}
}

在这里插入图片描述

8. 图像二值化

8.1 什么是图像二值化

图像二值化(Image Binarization)顾名思义,就是将图像上的灰度值设置为只有两个可能的值:通常是0(黑色)或 255(白色)。整个图像呈现出明显的只有黑和白的视觉效果。

本质: 通过设定一个阈值 T ,将图像中的所有像素点分为两类:

  • 大于阈值 T 的像素点: 被设置为 255(白色,代表前景或目标)

  • 小于等于阈值 T 的像素点: 被置为 0 (黑色,代表背景)

原始图像(灰度图)的每个像素值在 0 - 255 之间,经过二值化处理后,图像矩形中只剩下 0 和 255 两种数值。这极大的简化了图像数据,减少了计算量,并且能够凸显出目标的轮廓和结构。

8.2 为什么需要二值化

二值化是很多高级图像处理任务的预处理步骤,其主要目的和优势包括:

  1. 简化信息,突出目标: 将感兴趣的目标(前景)与背景分离开,忽略不必要的细节(如颜色、渐变灰度)。

  2. 大幅减少数据量: 图像从 256 个灰度级别减少到 2 两个,数据量显著减少,后续处理速度更快。

  3. 为后续操作做准备: 它是很多操作的前提,例如:

    • 轮廓查找: OpenCV 的轮廓查找函数通常需要再二值图像上进行。

    • 字符识别(OCR): 识别文字前,需要现将包含文字的图像二值化,将文字与纸张背景分离。

    • 图像分割: 将物体从背景中分离出来

    • 计算物体的面积、周长、位置等形态学分析

8.3 OpenCV 中的阈值化(二值化)方法

OpenCV 提供了 cv::threshold() 函数来进行类型的阈值化操作,其中最核心的就是二值化。

函数原型

double threshold(InputArray src, OutputArray dst, double thresh, double maxval, int type);

参数详解:

  • src: 输入图像,必须是单通道灰度图像。如果是彩色图像,需要先转换为灰度图(cv2.cvtColor(img, cv2.COLOR_BGR2GRAY))。

  • dst: 输出图像(二值化后的结果),与输入图像具有相同的大小和类型。

  • thresh: 设定的阈值(0~255)。

  • maxval: 当像素值超过(或小于,根据 type 决定)阈值时,所赋予的新值。对于二值化,通常设为 255。

  • type: 阈值化的类型,它决定了如何应用阈值。这是最关键的参数:

    • cv2.THRESH_BINARY (最常用)

    • cv2.THRESH_BINARY_INV

    • cv2.THRESH_TRUNC

    • cv2.THRESH_TOZERO

    • cv2.THRESH_TOZERO_INV

    • cv2.THRESH_OTSU (通常与上述类型组合使用,如 cv2.THRESH_BINARY + cv2.THRESH_OTSU)

    • cv2.THRESH_TRIANGLE

8.4 关键的阈值化类型详解

我们重点看与二值化最相关的几种类型。假设阈值为 T,最大值为 M(通常是255)。

  1. 简单阈值(全局阈值)

    这种方法对整个图像使用同一个固定阈值 T

    • cv2.THRESH_BINARY (标准二值化)

      • 公式:dst(x, y) = M if src(x, y) > T else 0

      • 解释:像素值大于阈值 T 的设为 M(白色),否则设为 0(黑色)。

      • 适用场景:光照均匀,背景和前景对比度非常明显的图像。

    • cv2.THRESH_BINARY_INV (反二值化)

      • 公式:dst(x, y) = 0 if src(x, y) > T else M

      • 解释:与 BINARY 相反。像素值大于阈值 T 的设为 0(黑色),否则设为 M(白色)。

      • 适用场景:想要目标为白色,背景为黑色的情况,但与 BINARY 的结果相反。

  2. 自适应阈值(局部阈值)

    简单阈值法的一个巨大缺陷是:它需要一个全局阈值 T,但对于光照不均或者背景复杂的图像,很难找到一个合适的 T 来很好地分割整个图像。

    解决方案自适应阈值(Adaptive Thresholding)。它不再是使用一个全局阈值,而是根据图像上每一个小区域(邻域)内的像素值,独自计算该区域的阈值。

    函数原型

    void cv::adaptiveThreshold(Mat src, Mat& dst, double maxValue, int adaptiveMethod, int thresholdType, int blockSize, double C)
    
    • src: 输入灰度图像。

    • dst: 输出图像。

    • maxValue: 满足条件的像素被赋予的最大值(通常是255)。

    • adaptiveMethod: 计算阈值的方法。

      • cv2.ADAPTIVE_THRESH_MEAN_C: 阈值是邻域区域的平均值减去常数 C

      • cv2.ADAPTIVE_THRESH_GAUSSIAN_C: 阈值是邻域区域的加权和(高斯窗口),权重是一个高斯核,再减去常数 C

    • thresholdType: 必须是 cv2.THRESH_BINARYcv2.THRESH_BINARY_INV

    • blockSize: 邻域大小(用来计算阈值的区域大小),必须是奇数(如 3, 5, 7, …)。

    • C: 从平均值或加权平均值中减去的常数。这是一个微调参数,用于优化结果。

    适用场景:光照不均、背景颜色变化较大的图像(如文档扫描、拍摄的纸张等)。

    简单阈值 vs 自适应阈值效果对比:
    (左:原图 | 中:简单阈值效果差 | 右:自适应阈值效果好)

  3. Otsu’s 方法(大津算法)

    简单阈值法另一个问题是:如何选择最佳阈值 T?手动尝试非常低效。

    解决方案Otsu’s 方法。这是一种自动确定图像最佳全局阈值的算法。其原理是最大化前景和背景两类之间的类间方差(inter-class variance)。方差越大,说明两部分差别越大,分割效果越好。

    使用方法:Otsu‘s 算法不是单独使用的,而是与 cv2.THRESH_BINARY 等组合使用。并且,cv2.threshold 函数会返回两个值,第一个就是 Otsu 算法计算出的最佳阈值。

    适用场景:图像具有双峰直方图(即图像的像素值分布可以明显看出有两个高峰)时,效果最好。

8.5 示例

public class ImageBinarizationActivity extends AppCompatActivity {private ActivityImageBinarizationBinding mBinding;static {System.loadLibrary("opencv_java4");}private Mat mBgr;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);mBinding = ActivityImageBinarizationBinding.inflate(getLayoutInflater());setContentView(mBinding.getRoot());try {mBgr = Utils.loadResource(this, R.drawable.lena);Imgproc.cvtColor(mBgr, mBgr, Imgproc.COLOR_BGR2GRAY);showMat(mBgr);} catch (IOException e) {throw new RuntimeException(e);}mBinding.btnBinary.setOnClickListener(view -> {threshold(Imgproc.THRESH_BINARY);});mBinding.btnBinaryInv.setOnClickListener(view -> {threshold(Imgproc.THRESH_BINARY_INV);});mBinding.btnTrunc.setOnClickListener(view -> {threshold(Imgproc.THRESH_TRUNC);});mBinding.btnTozero.setOnClickListener(view -> {threshold(Imgproc.THRESH_TOZERO);});mBinding.btnTozeroInv.setOnClickListener(view -> {threshold(Imgproc.THRESH_TOZERO_INV);});mBinding.btnOtsu.setOnClickListener(view -> {threshold2(Imgproc.THRESH_BINARY + Imgproc.THRESH_OTSU);});mBinding.btnTrlangle.setOnClickListener(view -> {threshold2(Imgproc.THRESH_BINARY + Imgproc.THRESH_TRIANGLE);});mBinding.btnOtsuInv.setOnClickListener(view -> {threshold2(Imgproc.THRESH_BINARY_INV + Imgproc.THRESH_OTSU);});mBinding.btnTriangleInv.setOnClickListener(view -> {threshold2(Imgproc.THRESH_BINARY_INV + Imgproc.THRESH_TRIANGLE);});mBinding.btnMeanC.setOnClickListener(view -> {Mat mBgrClone = mBgr.clone();Mat result = new Mat();// 自适应阈值 - 均值方法Imgproc.adaptiveThreshold(mBgrClone, result, 255,Imgproc.ADAPTIVE_THRESH_MEAN_C, Imgproc.THRESH_BINARY, 11, 2);showMat(result);mBgrClone.release();result.release();});mBinding.btnGaussianC.setOnClickListener(view -> {Mat mBgrClone = mBgr.clone();Mat result = new Mat();// 自适应阈值 - 高斯方法Imgproc.adaptiveThreshold(mBgrClone, result, 255,Imgproc.ADAPTIVE_THRESH_GAUSSIAN_C, Imgproc.THRESH_BINARY, 11, 2);showMat(result);mBgrClone.release();result.release();});}private void threshold(int type) {Mat mBgrClone = mBgr.clone();Imgproc.threshold(mBgrClone, mBgrClone, 127, 255, type);showMat(mBgrClone);mBgrClone.release();}private void threshold2(int type) {Mat mBgrClone = mBgr.clone();Imgproc.threshold(mBgrClone, mBgrClone, 0, 255, type);showMat(mBgrClone);mBgrClone.release();}private void showMat(Mat source) {Bitmap bitmap = Bitmap.createBitmap(source.cols(), source.rows(), Bitmap.Config.ARGB_8888);Utils.matToBitmap(source, bitmap);mBinding.ivSource.setImageBitmap(bitmap);}@Overrideprotected void onDestroy() {mBgr.release();super.onDestroy();}
}

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/94238.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/94238.shtml
英文地址,请注明出处:http://en.pswp.cn/pingmian/94238.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

人工智能之数学基础:离散型随机变量

本文重点 前面我们介绍了随机变量具有两个类型,一个类型是离散型随机变量,另外一个类型是连续型随机变量。我们先来学习离散型随机变量。 离散型随机变量 离散型随机变量 X 所有可能取的值为X1,X2...,并且有: 则称 p1 , p2, … 为离散型随机变量 X 的概率质量函数。其中 …

【Java开发日记】我们来讲一讲 Channel 和 FileChannel

目录 Channel FileChannel 打开 FileChannel 从 FileChannel 读数据 写数据到 FileChannel 关闭 FileChannel 示例 读数据 写数据 Channel 在 NIO 中&#xff0c;Channel 和 Buffer 是相辅相成的&#xff0c;只能从 Channel 读取数据到 Buffer 中&#xff0c;或者从 …

【力扣】2623. 记忆函数——函数转换

【力扣】2623. 记忆函数——函数转换 文章目录【力扣】2623. 记忆函数——函数转换一、题目二、解决方案1、概述1.1纯函数2、在Web开发中的记忆化用途2.1缓存网站文件&#xff08;1&#xff09;React 组件&#xff08;2&#xff09;缓存 API 调用3、算法中的记忆化4、专业实现的…

数据结构 -- 队列

队列的核心定义队列是受限线性表&#xff0c;仅允许在一端&#xff08;队尾&#xff09;插入元素、另一端&#xff08;队头&#xff09;删除元素&#xff0c;遵循 “先进先出&#xff08;FIFO&#xff0c;First In First Out&#xff09;” 原则。队列的结构与操作端队尾&#…

为什么hive在处理数据时,有的累加是半累加数据

在 Hive 处理数据时&#xff0c;“半累加数据” 指的是部分字段保留历史状态、部分字段随业务变化累加或更新的场景&#xff0c;这种模式广泛存在于需要兼顾 “历史追溯” 和 “增量更新” 的业务中。以下是具体例子&#xff0c;帮助理解其本质&#xff1a;例子 1&#xff1a;用…

【贪心算法】day2

&#x1f4dd;前言说明&#xff1a; 本专栏主要记录本人的贪心算法学习以及LeetCode刷题记录&#xff0c;按专题划分每题主要记录&#xff1a;&#xff08;1&#xff09;本人解法 本人屎山代码&#xff1b;&#xff08;2&#xff09;优质解法 优质代码&#xff1b;&#xff…

Spring Boot整合RabbitMQ进阶实战:TTL、死信队列与延迟队列深度解析

Spring Boot整合RabbitMQ进阶实战&#xff1a;TTL、死信队列与延迟队列深度解析 一、TTL机制深度解析&#xff1a;从原理到落地 在RabbitMQ的消息生命周期管理中&#xff0c;TTL&#xff08;Time-To-Live&#xff09; 是核心机制之一——它通过设置消息的"存活时长"&…

最新react,vue 解决无法使用js触发点击,解决方案

const elements document.getElementsByClassName(remove-btn-eIaRy9 select-none semi-dropdown-item);if (elements.length > 0) {const element elements[0];const rect element.getBoundingClientRect();// 模拟鼠标移动到元素上const mouseOverEvent document.crea…

一键部署开源 Coze Studio

文章目录一、简介1、什么是 Coze Studio2、参考地址二、安装部署1、安装docker2、安装git3、下载core4、配置公网可用5、登录成功一、简介 1、什么是 Coze Studio Coze Studio 是一站式 AI Agent 开发工具。提供各类最新大模型和工具、多种开发模式和框架&#xff0c;从开发到…

Python Excel 通用筛选函数

案例目的 第一个函数从指定文件路径读取CSV数据并转换为DataFrame&#xff0c;第二个函数使用灵活的条件筛选DataFrame。 示例数据!&idxMarketCURRPMTERMANT……*1JPUSD10…*1CHINAEUR00…*1USAUSD10…*2JPJPY10…*3USACNY11…*4CHINACNY00…*5JPUSD11…*6JPJPY00…假定数据…

鸿蒙中内存泄漏分析

引言&#xff1a;什么是内存泄漏&#xff1f; 想象一下你的手机是一个酒店&#xff0c;每个应用程序都是酒店的客人。当客人&#xff08;应用程序&#xff09;使用房间&#xff08;内存&#xff09;时&#xff0c;酒店经理&#xff08;系统&#xff09;会分配房间给他们使用。…

将windows 的路径挂载到Ubuntu上进行直接访问

1、下载hane NFS Server安装2、安装后打开3、在电脑上创建个共享文件夹&#xff0c;我这里选择D:\share4、在hane win nfs server 软件上选择Edit\preferences5、选择exports6、选择Edit exports file, 在最后添加D:\share -name:nfs&#xff0c;然后点击Save如果添加root权限使…

开源 python 应用 开发(十一)短语音转文本

最近有个项目需要做视觉自动化处理的工具&#xff0c;最后选用的软件为python&#xff0c;刚好这个机会进行系统学习。短时间学习&#xff0c;需要快速开发&#xff0c;所以记录要点步骤&#xff0c;防止忘记。 链接&#xff1a; 开源 python 应用 开发&#xff08;一&#xf…

【C++闯关笔记】封装②:友元与模板

系列文章目录 第零篇&#xff1a;从C到C入门&#xff1a;C有而C语言没有的基础知识总结-CSDN博客 第一篇&#xff1a;【C闯关笔记】封装①&#xff1a;类与对象-CSDN博客 第二篇&#xff1a;【C闯关笔记】封装②&#xff1a;友元与模板-CSDN博客 第三篇&#xff1a;【C闯关笔…

Python 爬虫教程 | 豆瓣 TOP250 数据抓取与分析实战

一、项目背景与数据价值豆瓣TOP250是影视行业的重要榜单&#xff0c;具有以下数据价值&#xff1a;评分与评价人数&#xff1a;衡量电影市场热度&#xff1b;导演与演员信息&#xff1a;分析人才价值与影视趋势&#xff1b;类型 / 地区 / 年份&#xff1a;洞察电影类型与年代变…

第04章 SPSS简介与数据库构建

参考&#xff1a;SPSS实战与统计思维 - 武松编著 - 微信读书 4.1 SPSS简介 发展历史 全称Statistical Product and Service Solutions&#xff0c;由美国斯坦福大学三位研究生于1968年开发。 对比其他软件成立时间&#xff1a;SAS&#xff08;1976年&#xff09;、Stata&…

【ABAP4】数据字典

ABAP数据字典ABAP数据字典概述数据字典的基本对象域数据元素表类型系统创建自定义透明表创建自定义结构锁对象ABAP数据字典概述 ABAP数据字典是SAP定义和管理数据的工具&#xff0c;包含了程序使用的所有对象&#xff0c;数据字典中包括数据库表、视图、数据类型、域、搜索帮助…

不知道Pycharm怎么安装?Pycharm安装教程(附安装包)

Pycharm安装教程&#xff08;附安装包&#xff09;获取方式&#xff1a;python开发工具包丨夸克网盘-资源免费下载 有位朋友刚开始学习python&#xff0c;不知道Pycharm要怎么安装&#xff0c;于是问我要一个安装教程。 先介绍一下Pycharm吧&#xff0c;PyCharm是一款python开…

在 Docker 容器中查看 Python 版本

博客目录前言方法一&#xff1a;交互式进入容器查看方法二&#xff1a;启动时直接执行命令方法三&#xff1a;启动后使用 exec 执行命令方法四&#xff1a;直接运行并查看版本&#xff08;容器退出&#xff09;方法比较与选择指南实际应用中的注意事项进阶技巧批量检查多个镜像…

React:Umi + React + Ant Design Pro的基础上接入Mock数据

为什么需要Mock数据 前端开发依赖后端接口时的阻塞问题 独立开发和测试的需求 快速迭代和原型验证的重要性 当前版本及框架 React18 Umi 4.0 Ant Design Ant Design Pro 其实这些都不重要&#xff0c;主要是有Umijs&#xff0c;因为Umijs具有开箱即用Mock功能的能力&#…