使用深度学习解决拍照文档复杂背景二值化问题

本文探讨了传统图像处理技术如大津法和自适应二值化在文档处理中的局限性,着重介绍了积分二值化和基于U-Net的深度学习方法在复杂光照和干扰情况下的优势,以及实际应用案例。通过对比不同方法,展示了深度学习在文档二值化中的优越性能和未来潜力。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

1.在手持拍照设备对文档进行拍照时,很容易出现光线不均、阴影、过暗等,或者有些旧的文档,古籍文档都有虫洞、透背、字迹不清现象,为了方便阅读、打印文档,或者OCR识别,这些干扰都对处理结果有很多不良的影响。
2.在文档处理过程,往往分这几步,图像预处理、文档图像二值化、版面分析、文本检测与识别等环节,在文档才二值这块,有很多传统的算法可以使用,比如大津法,自适应二值化等,但在使用的过程,这些传统的算法只对针对某些特定的干扰做深度调参,并不能达到对所有文档都有高鲁棒性。

一.传统方法

1.传统数字图像处理里面,对图像二值化有好多用可用办法,我自己试过几种方法,从最常用的大津法、自适应二值化,到比较偏门的积分二值化。下面来对比下这几种方法的效果。

效果图像第一是灰度图像,第二张是自适应二值化,第三张是大津法,第四张是积分二值化。

第一种场景,手写文档,光线不均,有少些阴影:
原图:
在这里插入图片描述
二值化效果图像:
在这里插入图片描述
第二种场景,带大面积阴影的印刷文档,而且阴影比较明显:
原图:
在这里插入图片描述

二值图像效果图:
在这里插入图片描述
第三种场景,有很重透背的文档:
原图:
在这里插入图片描述

二值图像效果图:
在这里插入图片描述
第四种场景,纸张带有底色的古籍手抄文档:
原图:
在这里插入图片描述

二值图像效果图:
在这里插入图片描述

2.从以上效果来看,当使用场景有干扰或者光线有变化的情况下,传统的图像图像处理并不能完美的解决文档图像二值化的问题,表现稍微好一些积分二值化(效果图像第四格)也不能胜任大部分环境。但有些使用场景相对稳定的情况下,可以选择积分二值化这个方法。下面是积分二值化的代码,是基于OpenCV C++写的。


/// <summary>
/// 积分二值化
/// </summary>
/// <param name="inputMat">输入图像</param>
/// <param name="thre">阈值(1.0)</param>
/// <param name="outputMat">输出图像</param>
void thresholdIntegral(cv::Mat& inputMat, double thre, cv::Mat& outputMat)
{
    // accept only char type matrices
    CV_Assert(!inputMat.empty());
    CV_Assert(inputMat.depth() == CV_8U);
    CV_Assert(inputMat.channels() == 1);
   
    outputMat = cv::Mat(inputMat.size(), CV_8UC1, 1);

    // rows -> height -> y
    int nRows = inputMat.rows;
    // cols -> width -> x
    int nCols = inputMat.cols;

    // create the integral image
    cv::Mat sumMat;
    cv::integral(inputMat, sumMat);

    CV_Assert(sumMat.depth() == CV_32S);
    CV_Assert(sizeof(int) == 4);

    int S = MAX(nRows, nCols) / 8;
    double T = 0.15;

    // perform thresholding
    int s2 = S / 2;
    int x1, y1, x2, y2, count, sum;

    // CV_Assert(sizeof(int) == 4);
    int* p_y1, * p_y2;
    uchar* p_inputMat, * p_outputMat;

    for (int i = 0; i < nRows; ++i)
    {
        y1 = i - s2;
        y2 = i + s2;

        if (y1 < 0)
        {
            y1 = 0;
        }
        if (y2 >= nRows)
        {
            y2 = nRows - 1;
        }

        p_y1 = sumMat.ptr<int>(y1);
        p_y2 = sumMat.ptr<int>(y2);
        p_inputMat = inputMat.ptr<uchar>(i);
        p_outputMat = outputMat.ptr<uchar>(i);

        for (int j = 0; j < nCols; ++j)
        {
            // set the SxS region
            x1 = j - s2;
            x2 = j + s2;

            if (x1 < 0)
            {
                x1 = 0;
            }
            if (x2 >= nCols)
            {
                x2 = nCols - 1;
            }

            count = (x2 - x1) * (y2 - y1);

            // I(x,y)=s(x2,y2)-s(x1,y2)-s(x2,y1)+s(x1,x1)
            sum = p_y2[x2] - p_y1[x2] - p_y2[x1] + p_y1[x1];

            if ((int)(p_inputMat[j] * count) < (int)(sum * (1.0 - T) * thre))
                p_outputMat[j] = 0;
            else
                p_outputMat[j] = 255;
        }
    }
}

3.当使用环境不确定或者使用环境比较复杂的时候,传统方法再什么调参也不能完美解决,这个时候只能考虑使用深度学习了。

二.基于U-net的图像二值化

1.Unet 网络
U-Net一开始就是针对生物医学图片的分割用的,一直到现在许多对医学图像的分割网络中,很大一部分会采取U-Net作为网络的主干。
算法部分我这里参考的U-Net关于视网膜血管分割这个项目,github地址:https://github.com/orobix/retina-unet 。我们可以看看它对视网膜血管分割。
在这里插入图片描述
2.深度学习框架用的Pytorch,参考了这个项目:https://github.com/milesial/Pytorch-UNet添加链接描述
3.但U-Net只能训练尺寸为512的图像,但对于拍摄的文档,如果把尺寸都压到512来做标签和训练,肯定会丢失好多细节上的东西,我这里把网络按ENet的结构做了微调。

三.模型推理

1.我的测试环境是win10,vs2019,OpenCV 4.5,模型推理这里为了方便,就直接用OpenCV的dnn。
2.代码:

DirtyDocUnet类:

#pragma once
#include <iostream>
#include <string>
#include <opencv2/opencv.hpp>
#include <opencv2/dnn/dnn.hpp>

class DirtyDocUnet
{
public:
	DirtyDocUnet(std::string _model_path);
	
	void dnnInference(cv::Mat &cv_src, cv::Mat &cv_dst);

	void docBin(const cv::Mat& cv_src, cv::Mat& cv_dst);
private:
	std::string model_path;
	cv::dnn::Net doc_net;
	int target_w = 1560;
	int target_h = 1560;
};
#include "DirtyDocUnet.h"

DirtyDocUnet::DirtyDocUnet(std::string _model_path)
{
	model_path = _model_path;
	doc_net = cv::dnn::readNet(model_path);
}

void DirtyDocUnet::dnnInference(cv::Mat &cv_src, cv::Mat &cv_dst)
{
	cv::Size reso(this->target_w,this->target_h);

	cv::Mat cv_gray;
	cv::cvtColor(cv_src, cv_gray, cv::COLOR_BGR2GRAY);
	cv::Mat bold = cv::dnn::blobFromImage(cv_gray, 1.0 / 255, reso, cv::Scalar(0, 0, 0), false, false);
	doc_net.setInput(bold);

	cv::Mat cv_out = doc_net.forward();

	cv::Mat cv_seg = cv::Mat::zeros(cv_out.size[2], cv_out.size[3], CV_8UC1);

	for (int i = 0; i < cv_out.size[2] * cv_out.size[3]; i++)
	{
		cv_seg.data[i] = cv_out.ptr<float>(0, 0)[i] * 255;
	} 
	cv::resize(cv_seg, cv_dst, cv_src.size());
}

/// <summary>
/// 二值图像的边缘光滑处理
/// </summary>
/// <param name="src">输入图像</param>
/// <param name="dst">输出图像</param>
/// <param name="uthreshold">宽度阈值</param>
/// <param name="vthreshold">高度阈值</param>
/// <param name="type">突出部的颜色,0表示黑色,1代表白色</param>
void deleteZigzag(cv::Mat& src, cv::Mat& dst, int uthreshold, int vthreshold, int type)
{
    //int threshold;
    src.copyTo(dst);
    int height = dst.rows;
    int width = dst.cols;
    int k;  //用于循环计数传递到外部
    for (int i = 0; i < height - 1; i++)
    {
        uchar* p = dst.ptr<uchar>(i);
        for (int j = 0; j < width - 1; j++)
        {
            if (type == 0)
            {
                //行消除
                if (p[j] == 255 && p[j + 1] == 0)
                {
                    if (j + uthreshold >= width)
                    {
                        for (int k = j + 1; k < width; k++)
                        {
                            p[k] = 255;
                        }
                    }
                    else
                    {
                        for (k = j + 2; k <= j + uthreshold; k++)
                        {
                            if (p[k] == 255)
                            {
                                break;
                            }
                        }
                        if (p[k] == 255)
                        {
                            for (int h = j + 1; h < k; h++)
                            {
                                p[h] = 255;
                            }
                        }
                    }
                }
                //列消除
                if (p[j] == 255 && p[j + width] == 0)
                {
                    if (i + vthreshold >= height)
                    {
                        for (k = j + width; k < j + (height - i) * width; k += width)
                        {
                            p[k] = 255;
                        }
                    }
                    else
                    {
                        for (k = j + 2 * width; k <= j + vthreshold * width; k += width)
                        {
                            if (p[k] == 255) break;
                        }
                        if (p[k] == 255)
                        {
                            for (int h = j + width; h < k; h += width)
                                p[h] = 255;
                        }
                    }
                }
            }
            else  //type = 1
            {
                //行消除
                if (p[j] == 0 && p[j + 1] == 255)
                {
                    if (j + uthreshold >= width)
                    {
                        for (int k = j + 1; k < width; k++)
                            p[k] = 0;
                    }
                    else
                    {
                        for (k = j + 2; k <= j + uthreshold; k++)
                        {
                            if (p[k] == 0) break;
                        }
                        if (p[k] == 0)
                        {
                            for (int h = j + 1; h < k; h++)
                                p[h] = 0;
                        }
                    }
                }
                //列消除
                if (p[j] == 0 && p[j + width] == 255)
                {
                    if (i + vthreshold >= height)
                    {
                        for (k = j + width; k < j + (height - i) * width; k += width)
                            p[k] = 0;
                    }
                    else
                    {
                        for (k = j + 2 * width; k <= j + vthreshold * width; k += width)
                        {
                            if (p[k] == 0) break;
                        }
                        if (p[k] == 0)
                        {
                            for (int h = j + width; h < k; h += width)
                                p[h] = 0;
                        }
                    }
                }
            }
        }
    }
}

void  DirtyDocUnet::docBin(const cv::Mat& cv_src, cv::Mat& cv_dst)
{
	if (cv_src.empty())
	{
		return;
	}
	std::vector<cv::Mat> cv_pieces;
	cv_pieces.push_back(cv_src(cv::Rect(0, 0, cv_src.cols, cv_src.rows / 2)));
	cv_pieces.push_back(cv_src(cv::Rect(0, cv_src.rows / 2, cv_src.cols, cv_src.rows / 2)));

	cv::Mat cv_pars;
	for (auto v : cv_pieces)
	{
		cv::Mat cv_temp;

		dnnInference(v, cv_temp);

		cv_pars.push_back(cv_temp);
	}
    cv::Mat cv_resize;
    cv::resize(~cv_pars, cv_resize, cv::Size(4096, 4096), cv::INTER_CUBIC);
    cv::Mat cv_zig;
    deleteZigzag(cv_resize, cv_zig, 5, 5, 0);
    cv::Mat cv_bin;

    cv::resize(~cv_zig, cv_dst, cv::Size(cv_src.cols, cv_src.rows), cv::INTER_LINEAR);
}

#include "DirtyDocUnet.h"

DirtyDocUnet::DirtyDocUnet(std::string _model_path)
{
	model_path = _model_path;
	doc_net = cv::dnn::readNet(model_path);
}

void DirtyDocUnet::dnnInference(cv::Mat &cv_src, cv::Mat &cv_dst)
{
	cv::Size reso(this->target_w,this->target_h);

	cv::Mat cv_gray;
	cv::cvtColor(cv_src, cv_gray, cv::COLOR_BGR2GRAY);
	cv::Mat bold = cv::dnn::blobFromImage(cv_gray, 1.0 / 255, reso, cv::Scalar(0, 0, 0), false, false);
	doc_net.setInput(bold);

	cv::Mat cv_out = doc_net.forward();

	cv::Mat cv_seg = cv::Mat::zeros(cv_out.size[2], cv_out.size[3], CV_8UC1);

	for (int i = 0; i < cv_out.size[2] * cv_out.size[3]; i++)
	{
		cv_seg.data[i] = cv_out.ptr<float>(0, 0)[i] * 255;
	} 
	cv::resize(cv_seg, cv_dst, cv_src.size());
}

/// <summary>
/// 二值图像的边缘光滑处理
/// </summary>
/// <param name="src">输入图像</param>
/// <param name="dst">输出图像</param>
/// <param name="uthreshold">宽度阈值</param>
/// <param name="vthreshold">高度阈值</param>
/// <param name="type">突出部的颜色,0表示黑色,1代表白色</param>
void deleteZigzag(cv::Mat& src, cv::Mat& dst, int uthreshold, int vthreshold, int type)
{
    //int threshold;
    src.copyTo(dst);
    int height = dst.rows;
    int width = dst.cols;
    int k;  //用于循环计数传递到外部
    for (int i = 0; i < height - 1; i++)
    {
        uchar* p = dst.ptr<uchar>(i);
        for (int j = 0; j < width - 1; j++)
        {
            if (type == 0)
            {
                //行消除
                if (p[j] == 255 && p[j + 1] == 0)
                {
                    if (j + uthreshold >= width)
                    {
                        for (int k = j + 1; k < width; k++)
                        {
                            p[k] = 255;
                        }
                    }
                    else
                    {
                        for (k = j + 2; k <= j + uthreshold; k++)
                        {
                            if (p[k] == 255)
                            {
                                break;
                            }
                        }
                        if (p[k] == 255)
                        {
                            for (int h = j + 1; h < k; h++)
                            {
                                p[h] = 255;
                            }
                        }
                    }
                }
                //列消除
                if (p[j] == 255 && p[j + width] == 0)
                {
                    if (i + vthreshold >= height)
                    {
                        for (k = j + width; k < j + (height - i) * width; k += width)
                        {
                            p[k] = 255;
                        }
                    }
                    else
                    {
                        for (k = j + 2 * width; k <= j + vthreshold * width; k += width)
                        {
                            if (p[k] == 255) break;
                        }
                        if (p[k] == 255)
                        {
                            for (int h = j + width; h < k; h += width)
                                p[h] = 255;
                        }
                    }
                }
            }
            else  //type = 1
            {
                //行消除
                if (p[j] == 0 && p[j + 1] == 255)
                {
                    if (j + uthreshold >= width)
                    {
                        for (int k = j + 1; k < width; k++)
                            p[k] = 0;
                    }
                    else
                    {
                        for (k = j + 2; k <= j + uthreshold; k++)
                        {
                            if (p[k] == 0) break;
                        }
                        if (p[k] == 0)
                        {
                            for (int h = j + 1; h < k; h++)
                                p[h] = 0;
                        }
                    }
                }
                //列消除
                if (p[j] == 0 && p[j + width] == 255)
                {
                    if (i + vthreshold >= height)
                    {
                        for (k = j + width; k < j + (height - i) * width; k += width)
                            p[k] = 0;
                    }
                    else
                    {
                        for (k = j + 2 * width; k <= j + vthreshold * width; k += width)
                        {
                            if (p[k] == 0) break;
                        }
                        if (p[k] == 0)
                        {
                            for (int h = j + width; h < k; h += width)
                                p[h] = 0;
                        }
                    }
                }
            }
        }
    }
}

void  DirtyDocUnet::docBin(const cv::Mat& cv_src, cv::Mat& cv_dst)
{
	if (cv_src.empty())
	{
		return;
	}
	std::vector<cv::Mat> cv_pieces;
	cv_pieces.push_back(cv_src(cv::Rect(0, 0, cv_src.cols, cv_src.rows / 2)));
	cv_pieces.push_back(cv_src(cv::Rect(0, cv_src.rows / 2, cv_src.cols, cv_src.rows / 2)));

	cv::Mat cv_pars;
	for (auto v : cv_pieces)
	{
		cv::Mat cv_temp;

		dnnInference(v, cv_temp);

		cv_pars.push_back(cv_temp);
	}
    cv::Mat cv_resize;
    cv::resize(~cv_pars, cv_resize, cv::Size(4096, 4096), cv::INTER_CUBIC);
    cv::Mat cv_zig;
    deleteZigzag(cv_resize, cv_zig, 5, 5, 0);
    cv::Mat cv_bin;

    cv::resize(~cv_zig, cv_dst, cv::Size(cv_src.cols, cv_src.rows), cv::INTER_LINEAR);
}

调用类
main.cpp

#include <iostream>
#include "DirtyDocUnet.h"

void thresholdIntegral(cv::Mat& inputMat, double thre, cv::Mat& outputMat);
void mergeImages(const cv::Mat& cv_src1, const cv::Mat& cv_src2, cv::Mat& cv_dst);

void imshow(std::string name, const cv::Mat& cv_src)
{
    cv::namedWindow(name, 0);
    int max_rows = 800;
    int max_cols = 800;
    if (cv_src.rows >= cv_src.cols && cv_src.rows > max_rows)
    {
        cv::resizeWindow(name, cv::Size(cv_src.cols * max_rows / cv_src.rows, max_rows));
    }
    else if (cv_src.cols >= cv_src.rows && cv_src.cols > max_cols)
    {
        cv::resizeWindow(name, cv::Size(max_cols, cv_src.rows * max_cols / cv_src.cols));
    }
    cv::imshow(name, cv_src);
}

int main(void)
{
    std::string path = "images";
    std::vector<std::string> filenames;
    cv::glob(path, filenames, false);
    std::string model_path = "models/unetv2.onnx";

    DirtyDocUnet doc_bin(model_path);
    int i = 0;

    for (auto v : filenames)
    {
        cv::Mat cv_src = cv::imread(v);
        cv::Mat cv_bin, cv_otsu, cv_gray, cv_integral;
        cv::cvtColor(cv_src, cv_gray, cv::COLOR_BGR2GRAY);

        cv::threshold(cv_gray, cv_bin, 127, 255, cv::THRESH_BINARY);
        cv::threshold(cv_gray, cv_otsu, 0, 255, cv::THRESH_OTSU);
        thresholdIntegral(cv_gray, 1.0, cv_integral);

        cv::Mat cv_unet;
        doc_bin.docBin(cv_src, cv_unet);

        cv_bin.push_back(cv_otsu);
        cv_integral.push_back(cv_unet);

        cv::Mat cv_all;
        mergeImages(cv_bin, cv_integral,cv_all);
        cv::imwrite(v, cv_all);
    }
}

/// <summary>
/// 积分二值化
/// </summary>
/// <param name="inputMat">输入图像</param>
/// <param name="thre">阈值(1.0)</param>
/// <param name="outputMat">输出图像</param>
void thresholdIntegral(cv::Mat& inputMat, double thre, cv::Mat& outputMat)
{
    // accept only char type matrices
    CV_Assert(!inputMat.empty());
    CV_Assert(inputMat.depth() == CV_8U);
    CV_Assert(inputMat.channels() == 1);
   
    outputMat = cv::Mat(inputMat.size(), CV_8UC1, 1);

    // rows -> height -> y
    int nRows = inputMat.rows;
    // cols -> width -> x
    int nCols = inputMat.cols;

    // create the integral image
    cv::Mat sumMat;
    cv::integral(inputMat, sumMat);

    CV_Assert(sumMat.depth() == CV_32S);
    CV_Assert(sizeof(int) == 4);

    int S = MAX(nRows, nCols) / 8;
    double T = 0.15;

    // perform thresholding
    int s2 = S / 2;
    int x1, y1, x2, y2, count, sum;

    // CV_Assert(sizeof(int) == 4);
    int* p_y1, * p_y2;
    uchar* p_inputMat, * p_outputMat;

    for (int i = 0; i < nRows; ++i)
    {
        y1 = i - s2;
        y2 = i + s2;

        if (y1 < 0)
        {
            y1 = 0;
        }
        if (y2 >= nRows)
        {
            y2 = nRows - 1;
        }

        p_y1 = sumMat.ptr<int>(y1);
        p_y2 = sumMat.ptr<int>(y2);
        p_inputMat = inputMat.ptr<uchar>(i);
        p_outputMat = outputMat.ptr<uchar>(i);

        for (int j = 0; j < nCols; ++j)
        {
            // set the SxS region
            x1 = j - s2;
            x2 = j + s2;

            if (x1 < 0)
            {
                x1 = 0;
            }
            if (x2 >= nCols)
            {
                x2 = nCols - 1;
            }

            count = (x2 - x1) * (y2 - y1);

            // I(x,y)=s(x2,y2)-s(x1,y2)-s(x2,y1)+s(x1,x1)
            sum = p_y2[x2] - p_y1[x2] - p_y2[x1] + p_y1[x1];

            if ((int)(p_inputMat[j] * count) < (int)(sum * (1.0 - T) * thre))
                p_outputMat[j] = 0;
            else
                p_outputMat[j] = 255;
        }
    }
}

void mergeImages(const cv::Mat& cv_src1, const cv::Mat& cv_src2, cv::Mat& cv_dst)
{
    CV_Assert(!(cv_src1.rows != cv_src2.rows || cv_src1.cols != cv_src2.cols));
    CV_Assert(!(cv_src1.empty() || cv_src2.empty()));

    cv_dst.create(cv_src1.rows, cv_src1.cols * 2, cv_src1.type());
    cv::Mat r1 = cv_dst(cv::Rect(0, 0, cv_src1.cols, cv_src1.rows));
    cv_src1.copyTo(r1);

    cv::Mat r2 = cv_dst(cv::Rect(cv_src1.cols, 0, cv_src1.cols, cv_src1.rows));
    cv_src2.copyTo(r2);
}

3.对比下处理效果
原图:
在这里插入图片描述
二值图像,第一张是自适应二值化,第二张是积分二值化,第三张是大津法二值化,第四张是UNet二值化的效果:
在这里插入图片描述

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

二值图像,第一张是自适应二值化,第二张是积分二值化,第三张是大津法二值化,第四张是UNet二值化的效果:
在这里插入图片描述

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

二值图像,第一张是自适应二值化,第二张是积分二值化,第三张是大津法二值化,第四张是UNet二值化的效果:
在这里插入图片描述
原图:
在这里插入图片描述
二值图像,第一张是自适应二值化,第二张是积分二值化,第三张是大津法二值化,第四张是UNet二值化的效果:
在这里插入图片描述
原图:
在这里插入图片描述
二值图像,第一张是自适应二值化,第二张是积分二值化,第三张是大津法二值化,第四张是UNet二值化的效果:
在这里插入图片描述
4.从整体的效果上看, 使用深度学习方法,最终的效果不管在什么样的环境在,都能得到一个不错的。其实这个效果还有可提升的空间,我当前用的训练集大概是2000张左右的样本,如果还能增加更多环境下的样本,那模型泛化会更好。
5.这个效果在一些手机扫描类APP里面也有类似的功能,一般叫省墨模式,或者黑白扫描,我们在安卓和iOS上都移植了这个算法,下面是我们iOS APP里面的效果,对移动端扫描APP感兴趣的可以去试试《扫描家》这个APP。
请添加图片描述
请添加图片描述

6.可执行文件和源码都上传到优快云,可执行文件把图像放到images,执行exe文件就在当前目录下保存几种效果的对比,
地址:https://download.youkuaiyun.com/download/matt45m/50653819
模型部署源码:https://download.youkuaiyun.com/download/matt45m/50654793
模型训练可以参考:计算机视觉——基于深度学习UNet实现的复杂背景文档二值化算法实现与模型训练

图像的二值化(需要是灰度图像) import cv2 as cv import numpy as np def threshold_demo(image): gray = cv.cvtColor(image,cv.COLOR_BGR2GRAY) #cv.THRESH_TRIANGLE与cv.THRESH_OTSU是自动寻找阈值,这个时候threshold值必须是0 #如果threshold不为0,即是自己指定的(如127),那么type就直接THRESH_BINARY就够了 ret,binary = cv.threshold(gray,0,255,cv.THRESH_BINARY|cv.THRESH_OTSU) print(ret) cv.imshow("binary",binary) def local_threshold(image): gray = cv.cvtColor(image, cv.COLOR_BGR2GRAY) binary =cv.adaptiveThreshold(gray,255,cv.ADAPTIVE_THRESH_GAUSSIAN_C,cv.THRESH_BINARY,25,10) cv.imshow("binary1", binary) src =cv.imread("E:/opencv/picture/dog.jpg") cv.imshow("initial_window",src) threshold_demo(src) local_threshold(src) cv.waitKey(0) cv.destroyAllWindows() 分析: 1. 全局阈值 def threshold_demo(image): gray = cv.cvtColor(image,cv.COLOR_BGR2GRAY) #cv.THRESH_TRIANGLE与cv.THRESH_OTSU是自动寻找阈值,这个时候threshold值必须是0 #如果threshold不为0,即是自己指定的(如127),那么type就直接THRESH_BINARY就够了 ret,binary = cv.threshold(gray,0,255,cv.THRESH_BINARY|cv.THRESH_OTSU) print(ret) cv.imshow("binary",binary) threshold其函数原型为:threshold(src, thresh, maxval, type[, dst]) -> retval, dst src参数表示输入图像(多通道,8位或32位浮点)。 thresh参数表示阈值。(如果type是自动二值化就设置为0) maxval参数表示与THRESH_BINARY和THRESH_BINARY_INV阈值类型一起使用设置的最大值。 type参数表示阈值类型。 retval参数表示返回的阈值。若是全局固定阈值算法,则返回thresh参数值。若是全局自适应阈值算法,则返回自适应计算得出的合适阈值。 dst参数表示输出与src相同大小和类型以及相同通道数的图像。 type阈值类型说明 cv.THRESH_BINARY | cv.THRESH_OTSU)#大律法,全局自适应阈值 参数0可改为任意数字但不起作用 cv.THRESH_BINARY | cv.THRESH_TRIANGLE)#TRIANGLE法,,全局自适应阈值, 参数0可改为任意数字但不起作用,适用于单个波峰 cv.THRESH_BINARY)# 自定义阈值为127,大于127的是白色 小于的是黑色 cv.THRESH_BINARY_INV)# 自定义阈值为127,大于127的是黑色 小于的是白色 cv.THRESH_TRUNC)# 截断 大于127的是改为127 小于127的保留 cv.THRESH_TOZERO)# 截断 小于127的是改为127 大于127的保留 2. 局部阈值法 3. def local_threshold(image): gray = cv.cvtColor(image, cv.COLOR_BGR2GRAY) binary =cv.adaptiveThreshold(gray,255,cv.ADAPTIVE_THRESH_GAUSSIAN_C,cv.THRESH_BINARY,25,10) cv.imshow("binary1", binary) adaptiveThreshold函数进行局部阈值 函数原型为:adaptiveThreshold(src, maxValue, adaptiveMethod, thresholdType, blockSize, C[, dst]) -> dst src参数表示输入图像(8位单通道图像)。 maxValue参数表示使用 THRESH_BINARY 和 THRESH_BINARY_INV 的最大值. adaptiveMethod参数表示自适应阈值算法, 平均— cv2.ADAPTIVE_THRESH_MEAN_C :领域内均值 高斯—cv2.ADAPTIVE_THRESH_GAUSSIAN_C :领域内像素点加权和,权 重为一个高斯窗口 thresholdType参数表示阈值类型,必须为THRESH_BINARY或THRESH_BINARY_INV的阈值类型。 blockSize参数表示块大小,规定领域大小(奇数且大于1,比如3,5,7........ )。 C参数是常数,表示从平均值或加权平均值中减去的数。 通常情况下,这是正值,但也可能为零或负值。 二.对超大图像进行二值化 如果这个时候只是单纯的用二值化api,图像上会出现很多噪声,所以我们特地介绍了一种对大图像进行二值化的方法import cv2 as cv import numpy as np from matplotlib import pyplot as plt #对超大图像进行二值化 def big_image_threshold(image): cw = 256 ch = 256 h,w = image.shape[:2] gray = cv.cvtColor(image,cv.COLOR_BGR2GRAY) for row in range(0, h, ch): for col in range(0, w, cw): #gray[0:3] 从索引0开始取,直到索引3为止,但不包括索引3。即索引0,1,2,正好是3个元素。 roi = gray[row:row+ch,col:col+ch]#表示从[row,col]到[row+ch,col+ch]的所有元素所组成的矩阵 #推荐使用局部阈值二值化 dst = cv.adaptiveThreshold(roi,255,cv.ADAPTIVE_THRESH_GAUSSIAN_C,cv.THRESH_BINARY,127,20) gray[row:row+ch,col:col+ch] =dst cv.imwrite("E:/opencv/picture/bigpicture1.jpg",gray) src = cv.imread("E:/opencv/picture/bigpicture.jpg") t1 = cv.getTickCount() big_image_threshold(src) t2 = cv.getTickCount() time= (t2-t1)/cv.getTickFrequency() #print("time =%sms\n"%(time)*1000) cv.waitKey(0) cv.destroyAllWindows()
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

知来者逆

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值