Skip to content

基本阈值处理操作

本教程中你将学到如何:

  • 使用 OpenCV 函数 cv::threshold 执行基本的阈值处理(thresholding)操作。

注意: 以下说明出自 Bradski 与 Kaehler 所著的 Learning OpenCV 一书。

  • 最简单的分割方法。
  • 应用示例:将图像中与我们要分析的目标相对应的区域分离出来。这种分离基于目标像素与背景像素之间的强度变化。
  • 为了把我们感兴趣的像素与其余像素(最终会被丢弃)区分开,我们将每个像素的强度值与一个阈值(threshold,根据要解决的问题来确定)进行比较。
  • 一旦正确地分离出了重要的像素,我们就可以为它们设定一个确定的值来标识它们(即可以把它们赋值为 00(黑)、255255(白)或任何符合你需求的值)。

Threshold_Tutorial_Theory_Example.jpg

  • OpenCV 提供了函数 cv::threshold 来执行阈值处理操作。
  • 用该函数可以实现 55 种类型的阈值处理操作,我们将在下面的小节中逐一说明。
  • 为了说明这些阈值处理过程的工作原理,假设我们有一幅源图像,其像素强度值为 src(x,y)src(x,y)。在下方的示意图中,水平蓝线代表阈值 threshthresh(固定值)。

Threshold_Tutorial_Theory_Binary.png

  • 该阈值处理操作可以表示为:

    dst(x,y)={maxValif src(x,y)>thresh0otherwise\texttt{dst}(x,y) = \begin{cases} \texttt{maxVal} & \text{if } \texttt{src}(x,y) > \texttt{thresh} \\ 0 & \text{otherwise} \end{cases}

  • 因此,如果像素 src(x,y)src(x,y) 的强度高于 threshthresh,则新像素的强度被设为 maxValmaxVal;否则像素被设为 00。

反二值阈值处理(Threshold Binary, Inverted)

Section titled “反二值阈值处理(Threshold Binary, Inverted)”

Threshold_Tutorial_Theory_Binary_Inverted.png

  • 该阈值处理操作可以表示为:

    dst(x,y)={0if src(x,y)>threshmaxValotherwise\texttt{dst}(x,y) = \begin{cases} 0 & \text{if } \texttt{src}(x,y) > \texttt{thresh} \\ \texttt{maxVal} & \text{otherwise} \end{cases}

  • 如果像素 src(x,y)src(x,y) 的强度高于 threshthresh,则新像素的强度被设为 00;否则被设为 maxValmaxVal。

Threshold_Tutorial_Theory_Truncate.png

  • 该阈值处理操作可以表示为:

    dst(x,y)={threshif src(x,y)>threshsrc(x,y)otherwise\texttt{dst}(x,y) = \begin{cases} \texttt{thresh} & \text{if } \texttt{src}(x,y) > \texttt{thresh} \\ \texttt{src}(x,y) & \text{otherwise} \end{cases}

  • 像素的最大强度值为 threshthresh;如果 src(x,y)src(x,y) 更大,则其值被截断。

Threshold_Tutorial_Theory_Zero.png

  • 该操作可以表示为:

    dst(x,y)={src(x,y)if src(x,y)>thresh0otherwise\texttt{dst}(x,y) = \begin{cases} \texttt{src}(x,y) & \text{if } \texttt{src}(x,y) > \texttt{thresh} \\ 0 & \text{otherwise} \end{cases}

  • 如果 src(x,y)src(x,y) 低于 threshthresh,则新像素值将被设为 00。

反阈值化为零(Threshold to Zero, Inverted)

Section titled “反阈值化为零(Threshold to Zero, Inverted)”

Threshold_Tutorial_Theory_Zero_Inverted.png

  • 该操作可以表示为:

    dst(x,y)={0if src(x,y)>threshsrc(x,y)otherwise\texttt{dst}(x,y) = \begin{cases} 0 & \text{if } \texttt{src}(x,y) > \texttt{thresh} \\ \texttt{src}(x,y) & \text{otherwise} \end{cases}

  • 如果 src(x,y)src(x,y) 大于 threshthresh,则新像素值将被设为 00。

本教程的代码如下所示。你也可以从 这里 下载 C++ 版本。

/**
* @file Threshold.cpp
* @brief Sample code that shows how to use the diverse threshold options offered by OpenCV
* @author OpenCV team
*/
#include "opencv2/imgproc.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui.hpp"
#include <iostream>
using namespace cv;
using std::cout;
/// Global variables
int threshold_value = 0;
int threshold_type = 3;
int const max_value = 255;
int const max_type = 4;
int const max_binary_value = 255;
Mat src, src_gray, dst;
const char* window_name = "Threshold Demo";
const char* trackbar_type = "Type: \n 0: Binary \n 1: Binary Inverted \n 2: Truncate \n 3: To Zero \n 4: To Zero Inverted";
const char* trackbar_value = "Value";
/**
* @function Threshold_Demo
*/
static void Threshold_Demo( int, void* )
{
/* 0: Binary
1: Binary Inverted
2: Threshold Truncated
3: Threshold to Zero
4: Threshold to Zero Inverted
*/
threshold( src_gray, dst, threshold_value, max_binary_value, threshold_type );
imshow( window_name, dst );
}
/**
* @function main
*/
int main( int argc, char** argv )
{
String imageName("stuff.jpg"); // by default
if (argc > 1)
{
imageName = argv[1];
}
src = imread( samples::findFile( imageName ), IMREAD_COLOR ); // Load an image
if (src.empty())
{
cout << "Cannot read the image: " << imageName << std::endl;
return -1;
}
cvtColor( src, src_gray, COLOR_BGR2GRAY ); // Convert the image to Gray
namedWindow( window_name, WINDOW_AUTOSIZE ); // Create a window to display results
createTrackbar( trackbar_type,
window_name, &threshold_type,
max_type, Threshold_Demo ); // Create a Trackbar to choose type of Threshold
createTrackbar( trackbar_value,
window_name, &threshold_value,
max_value, Threshold_Demo ); // Create a Trackbar to choose Threshold value
Threshold_Demo( 0, 0 ); // Call the function to initialize
/// Wait until the user finishes the program
waitKey();
return 0;
}

Java 版本可从 这里 下载。

import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.Image;
import javax.swing.BoxLayout;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.opencv.core.Core;
import org.opencv.core.Mat;
import org.opencv.highgui.HighGui;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
public class Threshold {
private static int MAX_VALUE = 255;
private static int MAX_TYPE = 4;
private static int MAX_BINARY_VALUE = 255;
private static final String WINDOW_NAME = "Threshold Demo";
private static final String TRACKBAR_TYPE = "<html><body>Type: <br> 0: Binary <br> "
+ "1: Binary Inverted <br> 2: Truncate <br> "
+ "3: To Zero <br> 4: To Zero Inverted</body></html>";
private static final String TRACKBAR_VALUE = "Value";
private int thresholdValue = 0;
private int thresholdType = 3;
private Mat src;
private Mat srcGray = new Mat();
private Mat dst = new Mat();
private JFrame frame;
private JLabel imgLabel;
public Threshold(String[] args) {
String imagePath = "../data/stuff.jpg";
if (args.length > 0) {
imagePath = args[0];
}
// Load an image
src = Imgcodecs.imread(imagePath);
if (src.empty()) {
System.out.println("Empty image: " + imagePath);
System.exit(0);
}
// Convert the image to Gray
Imgproc.cvtColor(src, srcGray, Imgproc.COLOR_BGR2GRAY);
// Create and set up the window.
frame = new JFrame(WINDOW_NAME);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// Set up the content pane.
Image img = HighGui.toBufferedImage(srcGray);
addComponentsToPane(frame.getContentPane(), img);
// Use the content pane's default BorderLayout. No need for
// setLayout(new BorderLayout());
// Display the window.
frame.pack();
frame.setVisible(true);
}
private void addComponentsToPane(Container pane, Image img) {
if (!(pane.getLayout() instanceof BorderLayout)) {
pane.add(new JLabel("Container doesn't use BorderLayout!"));
return;
}
JPanel sliderPanel = new JPanel();
sliderPanel.setLayout(new BoxLayout(sliderPanel, BoxLayout.PAGE_AXIS));
sliderPanel.add(new JLabel(TRACKBAR_TYPE));
// Create Trackbar to choose type of Threshold
JSlider sliderThreshType = new JSlider(0, MAX_TYPE, thresholdType);
sliderThreshType.setMajorTickSpacing(1);
sliderThreshType.setMinorTickSpacing(1);
sliderThreshType.setPaintTicks(true);
sliderThreshType.setPaintLabels(true);
sliderPanel.add(sliderThreshType);
sliderPanel.add(new JLabel(TRACKBAR_VALUE));
// Create Trackbar to choose Threshold value
JSlider sliderThreshValue = new JSlider(0, MAX_VALUE, 0);
sliderThreshValue.setMajorTickSpacing(50);
sliderThreshValue.setMinorTickSpacing(10);
sliderThreshValue.setPaintTicks(true);
sliderThreshValue.setPaintLabels(true);
sliderPanel.add(sliderThreshValue);
sliderThreshType.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
JSlider source = (JSlider) e.getSource();
thresholdType = source.getValue();
update();
}
});
sliderThreshValue.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
JSlider source = (JSlider) e.getSource();
thresholdValue = source.getValue();
update();
}
});
pane.add(sliderPanel, BorderLayout.PAGE_START);
imgLabel = new JLabel(new ImageIcon(img));
pane.add(imgLabel, BorderLayout.CENTER);
}
private void update() {
Imgproc.threshold(srcGray, dst, thresholdValue, MAX_BINARY_VALUE, thresholdType);
Image img = HighGui.toBufferedImage(dst);
imgLabel.setIcon(new ImageIcon(img));
frame.repaint();
}
public static void main(String[] args) {
// Load the native OpenCV library
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
// Schedule a job for the event dispatch thread:
// creating and showing this application's GUI.
javax.swing.SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
new Threshold(args);
}
});
}
}

Python 版本可从 这里 下载。

from __future__ import print_function
import cv2 as cv
import argparse
max_value = 255
max_type = 4
max_binary_value = 255
trackbar_type = 'Type: \n 0: Binary \n 1: Binary Inverted \n 2: Truncate \n 3: To Zero \n 4: To Zero Inverted'
trackbar_value = 'Value'
window_name = 'Threshold Demo'
def Threshold_Demo(val):
#0: Binary
#1: Binary Inverted
#2: Threshold Truncated
#3: Threshold to Zero
#4: Threshold to Zero Inverted
threshold_type = cv.getTrackbarPos(trackbar_type, window_name)
threshold_value = cv.getTrackbarPos(trackbar_value, window_name)
_, dst = cv.threshold(src_gray, threshold_value, max_binary_value, threshold_type )
cv.imshow(window_name, dst)
parser = argparse.ArgumentParser(description='Code for Basic Thresholding Operations tutorial.')
parser.add_argument('--input', help='Path to input image.', default='stuff.jpg')
args = parser.parse_args()
# Load an image
src = cv.imread(cv.samples.findFile(args.input))
if src is None:
print('Could not open or find the image: ', args.input)
exit(0)
# Convert the image to Gray
src_gray = cv.cvtColor(src, cv.COLOR_BGR2GRAY)
# Create a window to display results
cv.namedWindow(window_name)
# Create Trackbar to choose type of Threshold
cv.createTrackbar(trackbar_type, window_name , 3, max_type, Threshold_Demo)
# Create Trackbar to choose Threshold value
cv.createTrackbar(trackbar_value, window_name , 0, max_value, Threshold_Demo)
# Call the function to initialize
Threshold_Demo(0)
# Wait until user finishes program
cv.waitKey()

我们来看看程序的总体结构:

  1. 载入一幅图像。如果它是 BGR 格式,则将其转换为灰度图。为此,记住我们可以使用函数 cv::cvtColor:

    String imageName("stuff.jpg"); // by default
    if (argc > 1)
    {
    imageName = argv[1];
    }
    src = imread( samples::findFile( imageName ), IMREAD_COLOR ); // Load an image
    if (src.empty())
    {
    cout << "Cannot read the image: " << imageName << std::endl;
    return -1;
    }
    cvtColor( src, src_gray, COLOR_BGR2GRAY ); // Convert the image to Gray

Threshold_Tutorial_Theory_Base_Figure.png

```java
String imagePath = "../data/stuff.jpg";
if (args.length > 0) {
imagePath = args[0];
}
// Load an image
src = Imgcodecs.imread(imagePath);
if (src.empty()) {
System.out.println("Empty image: " + imagePath);
System.exit(0);
}
// Convert the image to Gray
Imgproc.cvtColor(src, srcGray, Imgproc.COLOR_BGR2GRAY);
```
```python
# Load an image
src = cv.imread(cv.samples.findFile(args.input))
if src is None:
print('Could not open or find the image: ', args.input)
exit(0)
# Convert the image to Gray
src_gray = cv.cvtColor(src, cv.COLOR_BGR2GRAY)
```
  1. 创建一个窗口以显示结果:

    namedWindow( window_name, WINDOW_AUTOSIZE ); // Create a window to display results
    // Create and set up the window.
    frame = new JFrame(WINDOW_NAME);
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    // Set up the content pane.
    Image img = HighGui.toBufferedImage(srcGray);
    addComponentsToPane(frame.getContentPane(), img);
    // Use the content pane's default BorderLayout. No need for
    // setLayout(new BorderLayout());
    // Display the window.
    frame.pack();
    frame.setVisible(true);
    # Create a window to display results
    cv.namedWindow(window_name)
  2. 创建 22 个滑动条(Trackbar)供用户输入:

    • 阈值处理类型:二值、化为零等等……
    • 阈值
    createTrackbar( trackbar_type,
    window_name, &threshold_type,
    max_type, Threshold_Demo ); // Create a Trackbar to choose type of Threshold
    createTrackbar( trackbar_value,
    window_name, &threshold_value,
    max_value, Threshold_Demo ); // Create a Trackbar to choose Threshold value
    sliderPanel.add(new JLabel(TRACKBAR_TYPE));
    // Create Trackbar to choose type of Threshold
    JSlider sliderThreshType = new JSlider(0, MAX_TYPE, thresholdType);
    sliderThreshType.setMajorTickSpacing(1);
    sliderThreshType.setMinorTickSpacing(1);
    sliderThreshType.setPaintTicks(true);
    sliderThreshType.setPaintLabels(true);
    sliderPanel.add(sliderThreshType);
    sliderPanel.add(new JLabel(TRACKBAR_VALUE));
    // Create Trackbar to choose Threshold value
    JSlider sliderThreshValue = new JSlider(0, MAX_VALUE, 0);
    sliderThreshValue.setMajorTickSpacing(50);
    sliderThreshValue.setMinorTickSpacing(10);
    sliderThreshValue.setPaintTicks(true);
    sliderThreshValue.setPaintLabels(true);
    sliderPanel.add(sliderThreshValue);
    # Create Trackbar to choose type of Threshold
    cv.createTrackbar(trackbar_type, window_name , 3, max_type, Threshold_Demo)
    # Create Trackbar to choose Threshold value
    cv.createTrackbar(trackbar_value, window_name , 0, max_value, Threshold_Demo)
  3. 等待用户输入阈值与阈值处理类型(或直到程序退出)。

  4. 每当用户改变任一滑动条的值时,函数 Threshold_Demo(Java 中为 update)就会被调用:

    static void Threshold_Demo( int, void* )
    {
    /* 0: Binary
    1: Binary Inverted
    2: Threshold Truncated
    3: Threshold to Zero
    4: Threshold to Zero Inverted
    */
    threshold( src_gray, dst, threshold_value, max_binary_value, threshold_type );
    imshow( window_name, dst );
    }
    private void update() {
    Imgproc.threshold(srcGray, dst, thresholdValue, MAX_BINARY_VALUE, thresholdType);
    Image img = HighGui.toBufferedImage(dst);
    imgLabel.setIcon(new ImageIcon(img));
    frame.repaint();
    }
    def Threshold_Demo(val):
    #0: Binary
    #1: Binary Inverted
    #2: Threshold Truncated
    #3: Threshold to Zero
    #4: Threshold to Zero Inverted
    threshold_type = cv.getTrackbarPos(trackbar_type, window_name)
    threshold_value = cv.getTrackbarPos(trackbar_value, window_name)
    _, dst = cv.threshold(src_gray, threshold_value, max_binary_value, threshold_type )
    cv.imshow(window_name, dst)

可以看到,这里调用了函数 cv::threshold。我们在 C++ 代码中给它传了 55 个参数:

  • src_gray:我们的输入图像
  • dst:目标(输出)图像
  • threshold_value:阈值处理操作所参照的 threshthresh 值
  • max_BINARY_value:二值阈值处理操作中使用的值(用于设定被选中的像素)
  • threshold_type:55 种阈值处理操作之一。它们列在上面函数的注释中。

Threshold_Tutorial_Original_Image.jpg Threshold_Tutorial_Result_Binary_Inverted.jpg Threshold_Tutorial_Result_Zero.jpg

  1. 编译该程序后,运行时把一幅图像的路径作为参数传入。

  2. 首先,我们尝试用反二值阈值处理图像。我们期望比 threshthresh 亮的像素会变暗,实际情况正是如此(从原图可以注意到,小狗的舌头和眼睛相比图像其他部分特别亮,这一点反映在输出图像中)。

  3. 现在我们尝试阈值化为零。我们期望最暗的像素(低于阈值)将变为全黑,而值大于阈值的像素将保持其原始值,输出图像的快照证实了这一点。