二值化是图像处理中一个比较基本又好重要的分割算法,opencv中自带了二个相关函数:
1.cv.threshold
2.cv.adaptiveThreshold
有时候,对于光线不均匀的图片,Otsu和自适应二值是对策。如下图:
明显的有一个从左上角到右下角的渐变的光路(事实也是blend了一个渐变的mask)。
对于上图二值化,显然自适应二值化是首选。效果如下:
但是效果不是特别的好。
网上有比较更加深入的算法:ImageBinarization
里面介绍了四种算法:
- Nat
- Niblack
- Nick
- Sauvola
其中,Nick是最好的。相关论文researchgate上可以下载。
代码如下:
// Copyright Jim Bosch 2011-2012.
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
/**
* A simple example showing how to wrap a couple of C++ functions that
* operate on 2-d arrays into Python functions that take NumPy arrays
* as arguments.
*
* If you find have a lot of such functions to wrap, you may want to
* create a C++ array type (or use one of the many existing C++ array
* libraries) that maps well to NumPy arrays and create Boost.Python
* converters. There's more work up front than the approach here,
* but much less boilerplate per function. See the "Gaussian" example
* included with Boost.NumPy for an example of custom converters, or
* take a look at the "ndarray" project on GitHub for a more complete,
* high-level solution.
*
* Note that we're using embedded Python here only to make a convenient
* self-contained example; you could just as easily put the wrappers
* in a regular C++-compiled module and imported them in regular
* Python. Again, see the Gaussian demo for an example.
*/
#include <boost/python/numpy.hpp>
#include <boost/scoped_array.hpp>
#include <iostream>
#include <numpy/ndarrayobject.h>
#include <opencv2/opencv.hpp>
#include "utils/conversion.h"
#include "NiblackBinarization.h"
#include "NickBinarization.h"
#include "SauvolaBinarization.h"
#include "NatBinarization.h"
using namespace ImageBinarization;
namespace p = boost::python;
namespace np = boost::python::numpy;
class Binarization
{
public:
Binarization() { ; }
public:
PyObject* Nat(np::ndarray const & array);
PyObject* Niblack(np::ndarray const & array, int kernelSize, double k);
PyObject* Nick(np::ndarray const & array, int kernelSize, double k);
PyObject* Sauvola(np::ndarray const & array, int kernelSize, double k);
};
PyObject* Binarization::Nat(np::ndarray const& array)
{
cv::Mat img;
if (array.get_dtype() != np::dtype::get_builtin<UINT8>()) {
PyErr_SetString(PyExc_TypeError, "Incorrect array data type");
p::throw_error_already_set();
}
if (array.get_nd() != 2) {
PyErr_SetString(PyExc_TypeError, "Incorrect number of dimensions");
p::throw_error_already_set();
}
if (!(array.get_flags() & np::ndarray::C_CONTIGUOUS)) {
PyErr_SetString(PyExc_TypeError, "Array must be row-major contiguous");
p::throw_error_already_set();
}
int * iter = reinterpret_cast<int*>(array.get_data());
int rows = array.shape(0);
int cols = array.shape(1);
img.create(cv::Size(cols, rows), CV_8U);
img.data = (uchar*)iter;
cv::Mat thresh;
cv::threshold(img, thresh, 120, 250, cv::THRESH_BINARY);
NatBinarization::Binarize(img, thresh);
//Mat -> Numpy
NDArrayConverter cvt;
PyObject* ret = cvt.toNDArray(thresh);
return ret;
}
PyObject* Binarization::Niblack(np::ndarray const& array, int kernelSize, double k)
{
cv::Mat img;
if (array.get_dtype() != np::dtype::get_builtin<UINT8>()) {
PyErr_SetString(PyExc_TypeError, "Incorrect array data type");
p::throw_error_already_set();
}
if (array.get_nd() != 2) {
PyErr_SetString(PyExc_TypeError, "Incorrect number of dimensions");
p::throw_error_already_set();
}
if (!(array.get_flags() & np::ndarray::C_CONTIGUOUS)) {
PyErr_SetString(PyExc_TypeError, "Array must be row-major contiguous");
p::throw_error_already_set();
}
int * iter = reinterpret_cast<int*>(array.get_data());
int rows = array.shape(0);
int cols = array.shape(1);
img.create(cv::Size(cols, rows), CV_8U);
img.data = (uchar*)iter;
cv::Mat thresh;
cv::threshold(img, thresh, 120, 250, cv::THRESH_BINARY);
NiblackBinarization::Binarize(img, thresh, kernelSize, k);
//Mat -> Numpy
NDArrayConverter cvt;
PyObject* ret = cvt.toNDArray(thresh);
return ret;
}
PyObject* Binarization::Nick(np::ndarray const& array, int kernelSize, double k)
{
cv::Mat img;
if (array.get_dtype() != np::dtype::get_builtin<UINT8>()) {
PyErr_SetString(PyExc_TypeError, "Incorrect array data type");
p::throw_error_already_set();
}
if (array.get_nd() != 2) {
PyErr_SetString(PyExc_TypeError, "Incorrect number of dimensions");
p::throw_error_already_set();
}
if (!(array.get_flags() & np::ndarray::C_CONTIGUOUS)) {
PyErr_SetString(PyExc_TypeError, "Array must be row-major contiguous");
p::throw_error_already_set();
}
int * iter = reinterpret_cast<int*>(array.get_data());
int rows = array.shape(0);
int cols = array.shape(1);
img.create(cv::Size(cols, rows), CV_8U);
img.data = (uchar*)iter;
cv::Mat thresh;
cv::threshold(img, thresh, 120, 250, cv::THRESH_BINARY);
NickBinarization::Binarize(img, thresh, kernelSize, k);
//Mat -> Numpy
NDArrayConverter cvt;
PyObject* ret = cvt.toNDArray(thresh);
return ret;
}
PyObject* Binarization::Sauvola(np::ndarray const& array, int kernelSize, double k)
{
cv::Mat img;
if (array.get_dtype() != np::dtype::get_builtin<UINT8>()) {
PyErr_SetString(PyExc_TypeError, "Incorrect array data type");
p::throw_error_already_set();
}
if (array.get_nd() != 2) {
PyErr_SetString(PyExc_TypeError, "Incorrect number of dimensions");
p::throw_error_already_set();
}
if (!(array.get_flags() & np::ndarray::C_CONTIGUOUS)) {
PyErr_SetString(PyExc_TypeError, "Array must be row-major contiguous");
p::throw_error_already_set();
}
int * iter = reinterpret_cast<int*>(array.get_data());
int rows = array.shape(0);
int cols = array.shape(1);
img.create(cv::Size(cols, rows), CV_8U);
img.data = (uchar*)iter;
cv::Mat thresh;
cv::threshold(img, thresh, 120, 250, cv::THRESH_BINARY);
SauvolaBinarization::Binarize(img, thresh, kernelSize, k);
//Mat -> Numpy
NDArrayConverter cvt;
PyObject* ret = cvt.toNDArray(thresh);
return ret;
}
BOOST_PYTHON_MODULE(myProcess) {
np::initialize(); // have to put this in any module that uses Boost.NumPy
p::class_<Binarization>("Binarization")
.def(p::init<>())
.def("Nat", &Binarization::Nat)
.def("Niblack", &Binarization::Niblack)
.def("Nick", &Binarization::Nick)
.def("Sauvola", &Binarization::Sauvola)
;
}
通过上述类的Wrapper,可以在Python中应用了。
测试代码如下:
import numpy as np
import cv2 as cv
import myProcess
image = cv.imread("d:/pic/letters002.JPG", cv.IMREAD_GRAYSCALE)
imageAdaptive = cv.adaptiveThreshold(image, 255, cv.ADAPTIVE_THRESH_GAUSSIAN_C, cv.THRESH_BINARY, 59, 5)
process = myProcess.Binarization()
imageNat = process.Nat(image)
imageNiblack = process.Niblack(image, 50, -1)
imageNick = process.Nick(image, 10, -0.2)
imageSauvola = process.Sauvola(image, 10, 0.2)
cv.namedWindow("Src", cv.WINDOW_NORMAL)
cv.namedWindow("Adaptive", cv.WINDOW_NORMAL)
cv.namedWindow("Nat", cv.WINDOW_NORMAL)
cv.namedWindow("Niblack", cv.WINDOW_NORMAL)
cv.namedWindow("Nick", cv.WINDOW_NORMAL)
cv.namedWindow("Sauvola", cv.WINDOW_NORMAL)
cv.imshow("Src", image)
cv.imshow("Adaptive", imageAdaptive)
cv.imshow("Nat", imageNat)
cv.imshow("Niblack", imageNiblack)
cv.imshow("Nick", imageNick)
cv.imshow("Sauvola", imageSauvola)
cv.waitKey(0)
cv.destroyAllWindows()
效果:
Nick:
Niblack:
显然,Nick效果是非常好的。
多谢,美。