使用积分图的自适应二值化算法
Adaptive Thresholding Using the Integral Image
对图像进行阈值处理的目的是将像素划分为 "暗 "或 “亮”。
自适应阈值处理是一种阈值处理形式,它考虑到了光照的空间变化,对图像中的光照变化具有更强的鲁棒性。
积分图像
参考:积分图像
积分图算法是一种快速计算图像区域和以及图像区域平方和的算法。
核心思想就是对每一个图像建立起自己的积分图查找表,在图像处理的阶段就可以根据预先建立积分图查找表直接查找从而实现对均值卷积的线性时间计算。
一张图像就是一个矩形,这个矩形中每个像素点的积分值,就是以图像左上角像素点为左上角顶点,以该像素点为右下角顶点的矩形中包含的所有元素之和。
通常不会对每一个像素点都重新计算矩形区域包含的所有元素值之和,而是利用相邻点的积分值实现快速计算。
I ( x , y ) = I ( x − 1 , y ) + I ( x , y − 1 ) − I ( x − 1 , y − 1 ) + p i x e l ( x , y ) I(x,y) = I(x-1,y) + I(x,y-1) -I(x-1,y-1)+ pixel(x,y)I(x,y)=I(x−1,y)+I(x,y−1)−I(x−1,y−1)+pixel(x,y)
点( x , y ) (x,y)(x,y)的积分值可以使用点( x − 1 , y ) (x-1,y)(x−1,y)与点( x , y − 1 ) (x,y-1)(x,y−1)的积分值之和,减去重叠区域,然后再加上点( x , y ) (x,y)(x,y)的像素值得到。
伪代码
自适应二值化
基本思想:取一个以目标点为中心的方块,如果目标点的像素小于这个方块像素平均值的T % T\%T%,就将目标点划分为 "暗 “,否则划分为"亮”。
令T = 1 − t T= 1-tT=1−t。
自适应二值化可以配置的参数有两个:
- 选取方块的边长s ss
- 阈值t tt
经验值:s ss取1/8 的图像宽度,t tt取15
使用积分图可以加快选取方块平均值的计算
区域D像素和的快速计算公式:
∑ x = x 1 x 2 ∑ y = y 1 y 2 f ( x , y ) = I ( x 2 , y 2 ) − I ( x 2 , y 1 − 1 ) − I ( x 1 − 1 , y 2 ) + I ( x 1 − 1 , y 1 − 1 ) \sum_{x=x_1}^{x_2}\sum_{y=y_1}^{y_2} f(x,y) = I(x_2,y_2)-I(x_2,y_1-1)-I(x_1-1,y_2)+I(x_1-1,y_1-1)x=x1∑x2y=y1∑y2f(x,y)=I(x2,y2)−I(x2,y1−1)−I(x1−1,y2)+I(x1−1,y1−1)
即
D = ( A + B + C + D ) − ( A + B ) − ( A + C ) + A D = (A+B+C+D) -(A+B)-(A+C) + AD=(A+B+C+D)−(A+B)−(A+C)+A
伪代码
代码实现
#include <iostream> #include <stdlib.h> using namespace std; /** 使用积分图像的自适应二值化 * @param in 输入图像 * @param in_w 输入图像的宽度 * @param in_h 输入图像的高度 * @param at_s 选取方块的边长 * @param at_t 阈值 * @param out 二值化后的结果 */ void AdaptiveThreshold(unsigned char* in, unsigned int in_w, unsigned int in_h, unsigned int at_s, unsigned char at_t, unsigned char* out) { unsigned int* int_data = (unsigned int*)malloc(sizeof(unsigned int) * in_w * in_h); unsigned int sum = 0; unsigned int x1, x2, y1, y2; unsigned int count; // Calc Integral Image for (unsigned int x = 0; x < in_w; x++) { sum = 0; for (unsigned int y = 0; y < in_h; y++) { sum += *(in + y * in_w + x); if (x == 0) { *(int_data + y * in_w + x) = sum; } else { *(int_data + y * in_w + x) = *(int_data + y * in_w + x - 1) + sum; } } } // Threshold for (unsigned int x = 0; x < in_w; x++) { for (unsigned int y = 0; y < in_h; y++) { x1 = x <= at_s / 2 ? 0 : (x - at_s / 2); x2 = x + at_s / 2; if (x2 > in_w - 1) x2 = in_w - 1; y1 = y <= at_s/2 ? 0: (y - at_s / 2); y2 = y + at_s / 2; if (y2 > in_h - 1) y2 = in_h - 1; count = (x2 - x1 +1 ) * (y2 - y1 + 1); if (x1 == 0 && y1 == 0) { sum = *(int_data + y2 * in_w + x2); } else if (x1 == 0) { sum = *(int_data + y2 * in_w + x2) - *(int_data + (y1 - 1) * in_w + x2); } else if (y1 == 0) { sum = *(int_data + y2 * in_w + x2) - *(int_data + y2 * in_w + x1 - 1); } else { sum = *(int_data + y2 * in_w + x2) - *(int_data + (y1 - 1) * in_w + x2) - *(int_data + y2 * in_w + x1 - 1) + *(int_data + (y1 - 1) * in_w + x1 - 1); } if (*(in + y * in_w + x) * count <= (sum * (100 - at_t) / 100)) { *(out + y * in_w + x) = 0; } else { *(out + y * in_w + x) = 255; } } } free(int_data); int_data = NULL; }
应用
#include <iostream> #include <stdlib.h> #include <opencv2/core.hpp> #include <opencv2/highgui.hpp> #include <opencv2/imgproc.hpp> int main() { cv::Mat img = cv::imread("F:\\OpencvTemplate\\img\\2.png"); cv::Mat gray; cv::cvtColor(img, gray, cv::COLOR_BGR2GRAY); cv::Mat out = cv::Mat::zeros(gray.size(), CV_8UC1); AdaptiveThreshold((uint8_t*)(gray.data), img.cols, img.rows, 32, 15, (uint8_t*)(out.data)); cv::namedWindow("threshold", cv::WINDOW_GUI_EXPANDED); cv::resizeWindow("threshold", img.cols / 3, img.rows / 3); cv::imshow("threshold", out); cv::waitKey(0); return 0; }