通过新图的坐标计算原图的坐标 o l d X = n e w X ∗ o l d W n e w W oldX=newX*\frac{oldW}{newW} oldX=newX∗newWoldW o l d Y = n e w Y ∗ o l d H n e w H oldY=newY*\frac{oldH}{newH} oldY=newY∗newHoldH
// 缩放图片(最近邻插值法)(部分区域有明显的锯齿状,不推荐使用) IMAGE Transform_shape_nearest(IMAGE im, unsigned int newWidth, unsigned int newHeight) { // 算法核心:通过新图的坐标计算原图的坐标
// 遍历整张新图 for (unsigned int i = 0; i < newWidth * newHeight; i++) { // 通过新图的坐标计算原图的坐标 fx = (i % newWidth) * ((float)im.w / newWidth); fy = (i / newWidth) * ((float)im.h / newHeight);
// 计算原图坐标的小数部分 dx = fx - (int)fx; dy = fy - (int)fy;
fx = (dx <= 0.5 ? (int)fx : (int)fx + 1); fy = (dy <= 0.5 ? (int)fy : (int)fy + 1); unsigned int k = fx + fy * im.w; // 注意乘的是原图的w
if (k >= im.w * im.h) // 判断k是否越界 k = im.w * im.h - 1;
令四个角上的点的一维坐标为k1,k2,k3,k4。所求点的小数部分为dx,dy可得 c o l o r = ( k 1 ⋅ ( 1 − d x ) + k 2 ⋅ d x ) ⋅ ( 1 − d y ) + ( k 3 ⋅ ( 1 − d x ) + k 4 ⋅ d x ) ⋅ d y color=\left(k_{1}\cdot\left(1-dx\right)+k_{2}\cdot dx\right)\cdot\left(1-dy\right)+\left(k_{3}\cdot\left(1-dx\right)+k_{4}\cdot dx\right)\cdot dy color=(k1⋅(1−dx)+k2⋅dx)⋅(1−dy)+(k3⋅(1−dx)+k4⋅dx)⋅dy
// 缩放图片(双线性插值法)(推荐使用) IMAGE Transform_shape_linear(IMAGE im, unsigned int newWidth, unsigned int newHeight) { float fx, fy, dx, dy; int k1, k2, k3, k4;
for (unsigned int i = 0; i < newWidth * newHeight; i++) { // 通过新图的坐标计算原图的坐标 fx = (i % newWidth) * ((float)im.w / newWidth); fy = (i / newWidth) * ((float)im.h / newHeight);
dx = fx - (int)fx; dy = fy - (int)fy;
fx = (int)fx; fy = (int)fy;
// 分别计算四个角上点的坐标 k1 = fx + fy * im.w; k2 = fx + 1 + fy * im.w; k3 = fx + (fy + 1) * im.w; k4 = fx + 1 + (fy + 1) * im.w;
x ′ ′ = x ′ cos θ + y ′ sin θ x''=x'\cos\theta+y'\sin\theta x′′=x′cosθ+y′sinθ y ′ ′ = y ′ cos θ − x ′ sin θ y''=y'\cos\theta-x'\sin\theta y′′=y′cosθ−x′sinθ
由图可知,旋转后的图片离第一象限, x x x轴偏移了 b x bx bx, y y y轴偏移了 b y by by,所以公式调整为
x ′ ′ = x ′ cos θ + y ′ sin θ + b x x''=x'\cos\theta+y'\sin\theta+bx x′′=x′cosθ+y′sinθ+bx y ′ ′ = y ′ cos θ − x ′ sin θ + b y y''=y'\cos\theta-x'\sin\theta+by y′′=y′cosθ−x′sinθ+by
c o l o r = b l u e ⋅ 144 + g r e e n ⋅ 587 + r e d ⋅ 299 1000 color=\frac{blue\cdot144+green\cdot587+red\cdot299}{1000} color=1000blue⋅144+green⋅587+red⋅299
3.1.2 最值法
c o l o r = m a x ( b l u e , g r e e n , r e d ) color=max(blue,green,red) color=max(blue,green,red)
3.1.2 均值法
c o l o r = b l u e + g r e e n + r e d 3 color=\frac{blue+green+red}{3} color=3blue+green+red
3.1.2 分量法
c o l o r = ( r e d / g r e e n / b l u e ) color=(red/green/blue) color=(red/green/blue)
// 彩色图转灰度图 void Transform_color_grayscale(IMAGE im, int grayscale_mode) { int color = 0; switch (grayscale_mode) { case GRAY_MODE_WEIGHT: { for (unsigned int i = 0; i < im.w * im.h; i++) { color = (im.color[i].blue * 114 + im.color[i].green * 587 + im.color[i].red * 299) / 1000; im.color[i].blue = color; im.color[i].green = color; im.color[i].red = color; } break; }
case GRAY_MODE_BEST: { for (unsigned int i = 0; i < im.w * im.h; i++) { color = im.color[i].blue; if (color < im.color[i].red) color = im.color[i].red; if (color < im.color[i].green) color = im.color[i].green; im.color[i].blue = color; im.color[i].green = color; im.color[i].red = color; } break; }
case GRAY_MODE_AVERAGE: { for (unsigned int i = 0; i < im.w * im.h; i++) { color = (im.color[i].blue + im.color[i].green + im.color[i].red) / 3; im.color[i].blue = color; im.color[i].green = color; im.color[i].red = color; } break; }
case GRAY_MODE_PART_RED: { for (unsigned int i = 0; i < im.w * im.h; i++) { im.color[i].blue = im.color[i].red; im.color[i].green = im.color[i].red; } break; }
case GRAY_MODE_PART_GREEN: { for (unsigned int i = 0; i < im.w * im.h; i++) { im.color[i].blue = im.color[i].green; im.color[i].red = im.color[i].green; } break; }
case GRAY_MODE_PART_BLUE: { for (unsigned int i = 0; i < im.w * im.h; i++) { im.color[i].green = im.color[i].blue; im.color[i].red = im.color[i].blue; } break; }
类间方差计算公式 g = w 前 1 − w 前 ⋅ ( u 前 − u 总 ) 2 g=\frac{w_{前}}{1-w_{前}}\cdot\left(u_{前}-u_{总}\right)^{2} g=1−w前w前⋅(u前−u总)2 w 前 w_{前} w前:前景像素数的占比 u 前 u_{前} u前:前景的平局灰度 u 总 u_{总} u总:图像的平局灰度 g g g:类间方差
整理可得 g = n 前 n 总 1 − n 前 n 总 ⋅ ( ∑ i = T i = 255 n i ⋅ i n 总 − ∑ i = 0 i = 255 n i ⋅ i n 总 ) 2 g=\frac{\frac{n_{前}}{n_{总}}}{1-\frac{n_{前}}{n_{总}}}\cdot\left(\frac{\sum_{i=T}^{i=255}n_{i}\cdot i}{n_{总}}-\frac{\sum_{i=0}^{i=255}n_{i}\cdot i}{n_{总}}\right)^{2} g=1−n总n前n总n前⋅(n总∑i=Ti=255ni⋅i−n总∑i=0i=255ni⋅i)2 n 前 n_{前} n前:前景的像素数 n 总 n_{总} n总:总的像素数 n i n_{i} ni:对应像素颜色的像素数
// 创建灰度直方图 for (unsigned int i = 0; i < im.w * im.h; i++) colorMap[im.color[i].blue] += 1; for (int i = 0; i < 256; i++) u += colorMap[i] * i; // u暂时计算累加 u /= (im.h * im.w);
// 遍历 0-255 寻找合适的阈值 for(unsigned int m = 0 ; m < 256; m++) { for (int n = m; n < 256; n++) { w0 += colorMap[n]; // w0暂时计算,保存前景的所有像素个数 u0 += colorMap[n] * n; // u0暂时计算累加 } u0 /= w0; w0 /= (im.h * im.w); if((w0 / (1 - w0) * (u0 - u)* (u0 - u)) > g) g = w0 / (1 - w0) * (u0 - u)* (u0 - u), T = m; } Transform_color_BW_DIY(im, T); }
请添加图片描述 3.2.3 三角法TRIANGLE
请添加图片描述
如图,在灰度直方图中,找到最低点和最高点,计算出这两点的函数(黄色直线),分别计算最低点和最高点之间的点到黄色直线的距离 d d d,当 d d d取到最大时,取到 T T T阈值。三角法TRIANGLE,适用单峰直方图。当图像的整体颜色差别不大时,不推荐使用
计算公式 d = ∣ A x 0 + B y 0 + C ∣ A 2 + B 2 d=\frac{\left|Ax_{0}+By_{0}+C\right|}{\sqrt{A^{2}+B^{2}}} d=A2+B2
∣Ax0+By0+C∣ y = k x + b y=kx+b y=kx+b 联立可得 d = ∣ − k x 0 + y 0 − b ∣ 1 + k 2 d=\frac{\left|-kx_{0}+y_{0}-b\right|}{\sqrt{1+k^{2}}} d=1+k2
∣−kx0+y0−b∣
// 二值图(三角法TRIANGLE,适用单峰直方图。当图像的整体颜色差别不大时,不推荐使用) void Transform_color_BW_TRIANGLE(IMAGE im) { int colorMap[256] = { 0 }; unsigned char minColor = 0; unsigned int minCount = 0; unsigned char maxColor = 0; unsigned int maxCount = 0; int d = 0; // 最短距离 unsigned char T = 0; // 阈值
// 创建灰度直方图 for (unsigned int i = 0; i < im.w * im.h; i++) colorMap[im.color[i].blue] += 1;
for (int i = 0; i < 256; i++) { if (im.color[i].blue < minColor) minColor = im.color[i].blue, minCount = colorMap[im.color[i].blue]; if (im.color[i].blue > maxColor) maxColor = im.color[i].blue, maxCount = colorMap[im.color[i].blue]; }
float k = ((float)maxCount - minCount) / ((float)maxColor - minColor); float b = maxCount - k * maxColor; // 遍历寻找最近距离 for (unsigned int n = minColor; n <= maxColor; n++) if (abs((int)(-k * n + colorMap[n] - b)) / sqrt((double)(1 + k * k)) > b) b = abs((int)(-k * n + colorMap[n] - b)) / sqrt((double)(1 + k * k)), T = n;
// 二值图(自适应阈值法,areaSize=25较合适,当图片线条多且密时,不推荐使用) IMAGE Transform_color_BW_Adaptive(IMAGE im, int areaSize) { // areaSize为区域的大小,区域越大,效果图的细节越好,areaSize=25较合适 BGRA* bgra = (BGRA*)malloc(sizeof(BGRA) * im.w * im.h); int* p = (int*)malloc(sizeof(int) * areaSize); // p->position 位置坐标 int k = (int)(sqrt((double)areaSize)) / 2; // 重合区域边长的一半
for (unsigned int i = 0; i < im.w * im.h; i++) { // 计算与卷积和对应重合区域的坐标 int t = 0; // 记录p的下标 for (int n = k; n >= -k; n--) for (int m = -k; m <= k; m++) { p[t] = ((i % im.w) + m) + (i / im.w + n) * im.w; t++; }
// 二值图(用二值图表示灰度变化,areaSize=25较合适) void Transform_color_BW_grayscale(IMAGE im, int areaSize) { // areaSize为区域的大小,区域越大,效果图的细节越好,areaSize=25较合适 int* p = (int*)malloc(sizeof(int) * areaSize); // p->position 位置坐标 int k = (int)(sqrt((double)areaSize)) / 2; // 重合区域边长的一半
for (unsigned int i = 0; i < im.w * im.h; i++) { // 计算与卷积和对应重合区域的坐标 int t = 0; // 记录p的下标 for (int n = k; n >= -k; n--) for (int m = -k; m <= k; m++) { p[t] = ((i % im.w) + m) + (i / im.w + n) * im.w; t++; }
c o l o r = ( R G B max − R G B min ) N ∑ i = 0 k n i color=\frac{\left(RGB\max-RGB\min\right)}{N}\sum_{i=0}^{k}ni color=N(RGBmax−RGBmin)i=0∑kni R G B max RGB\max RGBmax是:最大的RGB值一般为255 R G B min RGB\min RGBmin是:最小的RGB值一般为0 N是:总的像素数一般为 w ∗ h w*h w∗h k k k 是:color的RGB值 ∑ i = 0 k n i \sum_{i=0}^{k}ni ∑i=0kni 是:所有小于color的RGB值的像素的个数之和
整理后的公式
c o l o r = 255 − 0 w ⋅ h ∑ i = 0 k n i color=\frac{255-0}{w\cdot h}\sum_{i=0}^{k}ni color=w⋅h255−0i=0∑kni
∑ n = 1 8 k n ⋅ p n < 255 ⋅ ∑ n = 1 8 k n \sum_{n=1}^{8}kn\cdot pn<255\cdot\sum_{n=1}^{8}kn n=1∑8kn⋅pn<255⋅n=1∑8kn kn是:卷积核中的数 pn是:卷积核所对应的图像上的数 注意:这八个数字不包含卷积核的中心
十字形腐蚀卷积核的公式 ( k 1 ⋅ p 1 + k 2 ⋅ p 2 + k 3 ⋅ p 3 + k 4 ⋅ p 4 ) < 255 ⋅ ( k 1 + k 2 + k 3 + k 4 ) \left(k_{1}\cdot p_{1}+k_{2}\cdot p_{2}+k_{3}\cdot p_{3}+k_{4}\cdot p_{4}\right)<255\cdot\left(k_{1}+k_{2}+k_{3}+k_{4}\right) (k1⋅p1+k2⋅p2+k3⋅p3+k4⋅p4)<255⋅(k1+k2+k3+k4) 注意:这里是方便读者理解,但实际代码实现中为了代码的通用性,是用原公式实现的。
∑ n = 1 8 k n ⋅ p n > = 255 \sum_{n=1}^{8}kn\cdot pn>=255 n=1∑8kn⋅pn>=255 kn是:卷积核中的数 pn是:卷积核所对应的图像上的数 注意:这八个数字不包含卷积核的中心
十字形膨胀卷积核的公式 ( k 1 ⋅ p 1 + k 2 ⋅ p 2 + k 3 ⋅ p 3 + k 4 ⋅ p 4 ) > = 255 \left(k_{1}\cdot p_{1}+k_{2}\cdot p_{2}+k_{3}\cdot p_{3}+k_{4}\cdot p_{4}\right)>=255 (k1⋅p1+k2⋅p2+k3⋅p3+k4⋅p4)>=255 注意:这里是方便读者理解,但实际代码实现中为了代码的通用性,是用原公式实现的。
//池化 IMAGE Pooling(IMAGE im, int lenght) { // lenght池化区域的边长 unsigned int width = im.w / lenght; unsigned int hight = im.h / lenght; BGRA* bgra = (BGRA*)malloc(sizeof(BGRA) * width * hight); int* p = (int*)malloc(sizeof(int) * lenght * lenght); // p->position 位置坐标 unsigned char maxColor = 0; // 保存区域内的最大颜色值 int k = 0; // 记录实际循环的次数,作为新图的坐标
for (unsigned int i = 0; i < im.w * im.h; i += lenght) { // 计算与卷积和对应重合区域的坐标 int t = 0; // 记录p的下标 for (int n = 0; n < lenght; n++) for (int m = 0; m < lenght; m++) p[t] = ((i % im.w) + m) + (i / im.w + n) * im.w, t++;
if (p[lenght * lenght - 1] >= im.w * im.h) // 判断上边界 break; else if (i / im.w != 0 && (i / im.w) % lenght != 0) // 判断到了中间行 { i += (lenght - 1) * im.w; continue; } else if ((p[lenght * lenght - 1] / im.w) - (p[0] / im.w) + 1 != lenght) // 判断右边界 { i = i / im.w * im.w + im.w * lenght - lenght; continue; } else { maxColor = im.color[p[0]].blue; // 计算最大颜色值 for (int j = 0; j < lenght * lenght; j++) if (im.color[p[j]].blue > maxColor) maxColor = im.color[p[j]].blue;
积分图是统计二值图中各个区域像素个数的矩阵数组,使用积分图可以非常快速的计算出任意矩形区域内的像素个数,极大的简化了计算。 请添加图片描述 积分图的统计规则是:统计原点到图上任意一点所组成的矩形区域中的像素个数,所以积分图数组的大小等于图片的象数个数。如图中的( a = > b a=>b a=>b)区域,( a = > c a=>c a=>c)区域,( a = > f a=>f a=>f)区域,( a = > e a=>e a=>e)区域,( a = > d a=>d a=>d)区域。
计算( f = > d f=>d f=>d)区域中的像素个数 ( f = > d ) = ( a = > d ) − ( a = > c ) − ( a = > e ) + ( a = > b ) (f=>d)=(a=>d)-(a=>c)-(a=>e)+(a=>b) (f=>d)=(a=>d)−(a=>c)−(a=>e)+(a=>b)