网站建设高职考题目,重庆市建设工程信息网 023dir,北京网站建设公司内江,做绿化生意有什么网站什么是边缘检测 边缘检测(Edge Detection)就是提取图像中的边缘点(Edge Point)。边缘点是与周围像素相比灰度值有阶跃变化或屋顶状变化的像素。边缘常存在于目标与背景之间、目标与目标之间、目标与其影子之间。 在图像处理和图像分析中#xff0c;经常要用到边缘(Edge)、边…什么是边缘检测 边缘检测(Edge Detection)就是提取图像中的边缘点(Edge Point)。边缘点是与周围像素相比灰度值有阶跃变化或屋顶状变化的像素。边缘常存在于目标与背景之间、目标与目标之间、目标与其影子之间。 在图像处理和图像分析中经常要用到边缘(Edge)、边界(Boundary)、轮廓(Contour)等术语。一般来说边缘指的是边缘点它不能被称为边缘线。边界指的是图像中不同区域之间的分界线比如不同灰度、不同颜色的区域之间的分界线它是线而不是点可以被称为边界线。轮廓一般是指目标的轮廓目标就是语义明确的区域轮廓一般是在二值图像中围绕白色区域的闭合曲线。 边缘检测算法和边界线检测算法一般作用于灰度图像对于二值图像进行边缘检测是没有意义的。轮廓一定是闭合的但边界线不一定闭合比如道路区域与道边植被的边界线边缘点最多是断断续续的线段不保证连续更不保证闭合。掌握边缘、边界、轮廓的准确术语是非常必要的。 边缘类型
边缘检测是一种邻域运算即一个像素是否是边缘点是由其所处的邻域决定的。在一定大小的邻域内边缘分为阶跃边缘(step edge)和屋顶状边缘(roof edge)两种类型。下面以一维信号为例分析这两种不同类型的边缘的导数特征 求导与差分
在边缘检测中导数的计算通常采用两种方法 将邻域从离散空间变换到连续空间得到解析描述然后进行求导操作**。** 具体做法是先将**邻域按照一定的数学模型(曲线拟合、曲面拟合)得到其在连续空间中的解析描述然后对此解析描述进行求导得到边缘点。解析描述求导得到的导数位置是有小数位的比如在位置4.17处取得导数最大值即边缘点的位置是在4.17而不是像素的整数坐标4。这样得到的边缘点位置精度能够小于1个像素因此又将此方法称之为亚像素(sub pixel)边缘检测。在已知数学模型的指导下目前工业界做到的边缘检测最高精度为1/50个像素。**亚像素边缘检测能够在大大节省硬件成本的同时得到高的边缘检测精度是图像测量中的常用方法。 直接用差分(difference)代替求导。导数的公式见下式如果令dx1即得到是差分描述式4-2是差分描述的x方向偏导数式(4-3)是差分描述的y方向偏导数。 边缘强度与边缘方向
导数是有大小也有方向的因此边缘也有强弱与方向分别叫做边缘强度(edge intensity)和边缘方向(edge direction)边缘强度即边缘的幅值(magnitude)。用M(x,y)代表边缘的强度θ(x,y)代表边缘的方向有 实例
我们该如何提取这张图片的边缘呢? 首先当然是需要我们写一个函数来把24位彩色图像转化为8位灰度值图像
//24位彩色图像转8位灰度值
//rgbImage原始图像
//grayImage输出灰度图像
//width,height图片的宽和高
void convertToGray(uint8_t* rgbImage, uint8_t* grayImage, int width, int height)
{for (int y 0; y height; y) {for (int x 0; x width; x) {// 获取当前像素的 RGB 分量uint8_t r rgbImage[3 * (y * width x) 0];uint8_t g rgbImage[3 * (y * width x) 1];uint8_t b rgbImage[3 * (y * width x) 2];// 计算灰度值常用的加权平均法// 这里使用的加权系数是常见的R: 0.299, G: 0.587, B: 0.114uint8_t gray (uint8_t)(0.299 * r 0.587 * g 0.114 * b);// 将灰度值写入灰度图像数组grayImage[y * width x] gray;}}
}我们来看看效果 那接下来就需要用边缘检测来提取边缘了
一阶微分算子 根据边缘类型及其导数特征可以设计不同的检测算法。下面讲述几种常用的边缘检测算法习惯上称为边缘检测算子(Operator)。当使用差分时一般写成模板的表示形式。 对于阶跃边缘而言边缘点处的导数特征是“一阶导数取极值”。若边缘点处的一阶导数为正值则其为最大值反之则为最小值即在边缘点处的导数绝对值最大。 基于一阶导数的边缘检测算子称为一阶微分算子常用的一阶微分算子有梯度算子、罗伯特算子、索贝尔算子、Prewitt算子Robinson算子、Kirsch算子等。 梯度算子
梯度的本意是一个向量矢量表示某一函数在该点处的方向导数沿着该方向取得最大值即函数在该点处沿着该方向此梯度的方向变化最快变化率最大为该梯度的模。梯度的含义和边缘点是一致的因此产生了边缘检测的梯度算子(Gradient Operator) 写成模板的情况就是如下图所示 但是上面我们只是给出了像素(x,y)的边缘强度称为梯度值但是它是不是边缘点还需要一定的约束条件比如设定当Gradient(x,y)≥threshold时像素(x,y)才是边缘点threshold称为阈值。 这就是用梯度算子计算的结果我们上代码看看
void RmwGradientGryImg(uint8_t* pGryImg, int width, int height, uint8_t* pGrdImg)
{uint8_t* pGry, * pGrd;int dx, dy;int x, y;for (y 0, pGry pGryImg, pGrd pGrdImg; y height - 1; y){for (x 0; x width - 1; x, pGry){dx *pGry - *(pGry 1);dy *pGry - *(pGry width);int gradient (int)(sqrt(dx * dx * 1.0 dy * dy));*pGrd (gradient 255) ? 255 : gradient;}*pGrd 0; //尾列不做,边缘强度赋0pGry;}memset(pGrd, 0, width); //尾行不做,边缘强度赋0
}这里简单说一下这个函数 函数参数 pGryImg输入的灰度图像数据指针。width图像的宽度。height图像的高度。pGrdImg输出的梯度图像数据指针。 双层循环 外层循环 for (y 0, pGry pGryImg, pGrd pGrdImg; yheight-1; y) 遍历图像的每一行pGry 和 pGrd 分别指向当前行的灰度图像数据和梯度图像数据。内层循环 for (x 0; xwidth-1; x, pGry) 遍历当前行的每个像素pGry 指向当前像素的灰度值。 计算梯度 梯度的计算采用的是简单的基于像素差值的方法分别计算水平方向和垂直方向的梯度。dx *pGry-*(pGry1)水平方向的梯度计算当前像素和右侧像素的灰度差值。dy *pGry-*(pGrywidth)垂直方向的梯度计算当前像素和下方像素的灰度差值。sqrt(dx*dx*1.0dy*dy)使用欧式距离公式计算梯度幅值。min(255, ...)确保梯度幅值不超过255限定在0到255之间。 内存清零 memset(pGrd, 0, width)清零输出图像的最后一行因为最后一行的梯度值未计算。 返回 函数返回处理完成。 看看实际运行的效果是什么样子的 效果并不是很好我们把原始图像做一下图像增强灰度值均衡化试试 换一种图像增强的方法加一下反相试试
先简单写一下反相的函数
void invertImage(uint8_t *image, int width, int height) {for (int i 0; i width * height; i) {image[i] 255 - image[i];}
}这次使用了线性拉伸并简单处理了一下参数 效果还算不错那如果加一下阈值呢就像上面说的我们重新写一下这个函数
//梯度算子加阈值
//pGryImg输入的灰度图像数据指针。
//width图像的宽度。
//height图像的高度。
//pGrdImg输出的梯度图像数据指针
void RmwGradientGryImgPlus(uint8_t* pGryImg, int width, int height, uint8_t* pGrdImg, int threshold)
{uint8_t* pGry, * pGrd;int dx, dy;int x, y;for (y 0, pGry pGryImg, pGrd pGrdImg; y height - 1; y){for (x 0; x width - 1; x, pGry){dx *pGry - *(pGry 1);dy *pGry - *(pGry width);int gradient (int)(sqrt(dx * dx * 1.0 dy * dy));*(pGrd) (gradient threshold) ? min(255, gradient) : 0;}*(pGrd) 0; //尾列不做,边缘强度赋0pGry;}memset(pGrd, 0, width); //尾行不做,边缘强度赋0
}看一下效果 画面确实变得更干净了 罗伯特算子
梯度算子的计算只涉及到了3个像素只在水平和垂直方向做差分。罗伯特算子Roberts Operator)给出了一个4个像素之间进行运算的算子分别在两个对角线方向做差分。 由于对角线上2个像素之间的距离为√2所以罗伯特算子的∆x和∆y采用对角线差分后不再采用√(∆_x2∆_y2 )其描述见下式。罗伯特算子取∆x绝对值与∆y绝对值中的最大值。 我们来看演示 那么罗伯特算子好在哪呢
罗伯特算子去掉了梯度算子的开方运算计算复杂度也降低了不少。
void RmwRobertsGryImg(uint8_t *pGryImg, int width, int height, uint8_t *pRbtImg)
{uint8_t *pGry, *pRbt;int dx, dy;int x, y;for (y 0, pGry pGryImg, pRbt pRbtImg; y height - 1; y){for (x 0; x width - 1; x, pGry){dx *pGry - *(pGry width 1);dy *(pGry 1) - *(pGry width);*pRbt (uint8_t)(dx dy ? dx : dy); // 使用三目运算符选择较大的值}*pRbt 0; // 尾列不做, 边缘强度赋0pGry;}memset(pRbt, 0, width); // 尾行不做, 边缘强度赋0
}我们来看结果 为什么看起来的效果好像还没有梯度算子的结果好呢
是因为图像太复杂没有滤波那么在边缘计算前能不能先滤波
我们来看下一个算子
索贝尔算子
在一幅噪声较大的图像中如果不进行图像平滑就进行边缘检测必然会在边缘图像中产生噪声干扰。
因此索贝尔算子(Sobel Operator)中在求∆x和∆y前先进行滤波。在求∆x前先执行如下图的所示的高斯均值滤波在求∆y前先执行如下图的所示的高斯均值滤波。 另外索贝尔算子进一步拉大进行差分的2个像素之间的距离∆x和∆y采用如下的模板形式 我们来看示意图 在图像1中红色方块代表当前像素(x,y)先执行图4-8(a)所示的高斯滤波用D ̅、E ̅代表滤波后的值则得到 执行∆x模板则有∆xD ̅-E ̅(A2DF)-(C2EH) 另外索贝尔算子在对∆x和∆y的使用上采用了它们的绝对值相加的形式 看效果 对原始灰度图像执行索贝尔算子得到的结果图中虚线框所示的边缘变成了双线宽。
我们来写代码
//索贝尔算子
void RmwSobelGryImg(uint8_t* pGryImg, int width, int height, uint8_t* pSbImg)
{uint8_t* pGry, * pSb;int dx, dy;int x, y;memset(pSbImg, 0, width); // 首行不做, 边缘强度赋0for (y 1, pGry pGryImg width, pSb pSbImg width; y height - 1; y){*pSb 0; // 首列不做, 边缘强度赋0pGry;for (x 1; x width - 1; x, pGry){// 求dxdx *(pGry - 1 - width) (*(pGry - 1) * 2) *(pGry - 1 width);dx - *(pGry 1 - width) (*(pGry 1) * 2) *(pGry 1 width);// 求dydy *(pGry - width - 1) (*(pGry - width) * 2) *(pGry - width 1);dy - *(pGry width - 1) (*(pGry width) * 2) *(pGry width 1);// 结果*pSb (uint8_t)min(255, abs(dx) abs(dy));}*pSb 0; // 尾列不做, 边缘强度赋0pGry;}memset(pSb, 0, width); // 尾行不做, 边缘强度赋0
}梯度算子、罗伯特算子、索贝尔算子的比较
以下从4个方面对梯度算子、罗伯特算子、索贝尔算子进行比较。
•1. 偏导数∆x和∆y的求取
梯度算子在3个像素之间进行运算只在水平和垂直方向做差分做差分的2个像素之间的距离为1。
罗伯特算子在4个像素之间进行运算分别在两个对角线方向做差分做差分的2个像素之间的距离为√2。
索贝尔算子在8个像素之间进行运算只在水平和垂直方向做差分做差分的2个像素之间的距离为2。
•2. 是否“先平滑后求导”
索贝尔算子在差分之前进行了加权均值滤波对图像进行平滑加权函数采用了高斯函数因此索贝尔具有滤除噪声的效果。梯度算子和罗伯特算子都没有进行平滑。“先平滑后求导”是边缘检测的通用策略一般在执行梯度算子和罗伯特算子前是需要使用另外的步骤做图像平滑索贝尔算子则是把平滑写到了算子中。
•3. 边缘强度的大小
按照式(4-4)边缘强度的定义梯度算子是严格遵守的罗伯特算子是取∆x绝对值和∆y绝对值中的最大值索贝尔算子是取∆x与∆y的绝对值之和。而且索贝尔算子在高斯滤波后没有除以4所以又相当于∆x、∆y放大了4倍。对于边缘强度罗伯特算子、梯度算子、索贝尔算子之间的数值关系大致如下 •4. 邻域与边缘宽度
梯度算子、罗伯特算子的计算只涉及到了2行2列所以它们得到的边缘宽度是1个像素索贝尔算子涉及到了3行3列所以它得到的边缘宽度是2个像素边缘变成了双线宽。
方向模板 若是能根据边缘的具体走向求偏导数则边缘强度值应该会更准确。 因此在实际应用中是先假定了有限的几个边缘方向再对这些假定的每个边缘方向设置一个特定的模板计算每个模板的边缘强度从中选择最大的边缘强度作为边缘强度的结果而且该最大边缘强度对应模板的方向就认为是边缘的方向。 常用基于方向模板的边缘检测算子有Prewitt算子Robinson算子、Kirsch算子。Prewitt算子使用4个方向模板Robinson算子和Kirsch算子都使用8个方向模板。这些算子都是先采用了均值滤波然后进行差分计算。 Prewitt算子
Prewitt算子设定了0°、45°、90°和135°共计4种边缘方向根据这4种边缘方向分别设计了4个模板 对每个像素分别计算这个4个模板的值取绝对值最大者作为该像素的边缘强度Prewitt(x,y)。同时该最大值对应模板的方向作为该像素的边缘方向(与边缘的走向相差90°因为显然边缘走向的法线方向上的导数最大)。若把这些模板中为“0”点(空白处)的连成一条直线可以发现这些模板强调了水平线、135°斜线、竖直线、45°斜线的检测。Prewitt算子强调对直线的检测对于上述走向的直线总有一个模板的输出值最大。
void RmwPrewittGryImg(uint8_t *pGryImg, int width, int height, uint8_t *pPRTImg)
{uint8_t *pGry, *pPRT;int dx, dy, d45, d135, v1, v2;int x, y;memset(pPRTImg, 0, width); // 首行不做, 边缘强度赋0for (y 1, pGry pGryImg width, pPRT pPRTImg width; y height - 1; y){*pPRT 0; // 首列不做, 边缘强度赋0pGry;for (x 1; x width - 1; x, pGry){// 求dxdx *(pGry - 1 - width) *(pGry - 1) *(pGry - 1 width);dx - *(pGry 1 - width) *(pGry 1) *(pGry 1 width);// 求dydy *(pGry - width - 1) *(pGry - width) *(pGry - width 1);dy - *(pGry width - 1) *(pGry width) *(pGry width 1);// 求45度d45 *(pGry - width - 1) *(pGry - width) *(pGry - 1);d45 - *(pGry width 1) *(pGry width) *(pGry 1);// 求135度d135 *(pGry - width) *(pGry - width 1) *(pGry 1);d135 - *(pGry width - 1) *(pGry width) *(pGry - 1);// 结果v1 abs(dx) abs(dy) ? abs(dx) : abs(dy);v2 abs(d45) abs(d135) ? abs(d45) : abs(d135);*pPRT (uint8_t)((v1 v2) ? ((v1 255) ? 255 : v1) : ((v2 255) ? 255 : v2));}*pPRT 0; // 尾列不做, 边缘强度赋0pGry;}memset(pPRT, 0, width); // 尾行不做, 边缘强度赋0
}我们看一下实现效果 Robinson算子
Robinson算子除(a)外的7个模板都是由其上个模板顺时针旋转1个像素得到的。若是把模板中的负数值合并成一个区域可以看出该算子强调了对角点的检测对于各种形状的角点总有一个模板的输出值最大。 有兴趣大家可以查一下啊这里就不再过多解释
二阶微分算子
一阶微分算子能够得到边缘强度但是需要再加上一定的条件约束比如设置个阈值才能判定一个像素是不是边缘点。
通过对边缘类型及其导数的分析可知阶跃边缘的导数特征除了“一阶导数取极值”外还有“二阶导数过零点”。因此可以采用二阶导数利用过零点得到边缘点这样就不需要其他的条件了。
拉普拉斯算子
拉普拉斯算子(Laplacian Operator)是近似给出二阶导数的流行方法其使用3×3的邻域给出了4-邻接和8-邻接的邻域的2种模板 对如图所示的原始灰度图像执行4邻域拉普拉斯算子得到的结果如图所示图中虚线框所示的位置上发生了过零点导数由负数变到了正数此处即边缘。 在拉普拉斯算子的结果图像中可以发现过零点位置刚好就是边缘的位置。由于过零点是在像素之间不在整数坐标上所以在提取边缘点时往往采取下面的策略
当一个像素的二阶导数大于0其邻域内有像素的二阶导数小于0或等于0则该像素被标记为边缘点。
沈俊算子
唯一一个以国人命名的算子 沈俊教授同样提出了先滤波后求导的边缘检测方法J. Shen and S. Castan, An optimal linear operator for step edge detection, CVGIP: Graphical Models and Image Processing, Vol. 54 No.2, Mar. 1992, pp.112 – 133即沈俊算子(ShenJun Edge Operator)。 沈教授在阶跃边缘和可加白噪声模型下就信噪比最大准则证明了图像平滑的最佳滤波器是对称的指数函数形式如下 显然当a_0越大时c2就越小T(j,i)就越陡越窄相当于滤波邻域就越小压制噪声的能力就弱图像模糊程度就越小边缘定位的精度就越高。
在算子实现上沈教授对图像分别按行、按列各进行两次先正方向再反方向的递推滤波实现(|j|、|i|的优点)等价于用上述指数函数进行图像滤波证明了滤波结果减去原始灰度值得到的差值乘以2c1〖ln〗^c2约等于其二阶导数的值。沈俊算子的实现过程如下
沈俊算子的实现过程
step.1 对每行从左向右进行
1(0,)(0,)
1(,)1(−1,)_0×((,)−1(−1,))1,2,⋯ℎ−1。
step.2 对每行从右向左进行
2(ℎ−1,)1(ℎ−1,)
2(,)2(1,)_0×(1(,)−2(1,))ℎ−2, ⋯, 1,0。
step.3 对每列从上向下进行
3(,0)2(,0)
3(,)3(,−1)_0×(2(,)−3(,−1))1,2,⋯ℎℎ−1。
step.4 对每列从下向上进行
4(,ℎℎ−1)3(,ℎℎ−1)
4(,)4(,1)_0×(3(,)−4(,1))ℎℎ−2, ⋯, 1,0。
step.5 对每个像素(,)执行(,)4(,)−(,)得到二阶导数(,)。
step.6 对每个像素(,)进行过零点检测得到边缘点。我们来看程序
//沈俊算子
//pGryImg 和 pTmpImg 是指向 uint8_t 类型的指针它们分别指向原始灰度图像数据和辅助图像数据。
//width 和 height 是整型参数表示图像的宽度和高度。
//a0 是双精度浮点型参数表示滤波系数。
//pSJImg 是指向 uint8_t 类型的指针它指向了输出的图像数据。
void RmwShenJunGryImg(uint8_t* pGryImg,uint8_t* pTmpImg, int width, int height, double a0, uint8_t* pSJImg)
{uint8_t* pGry, * pCur, * pSJ, * pEnd;int LUT[512], * ALUT; // a0查找表int x, y, pre, dif;// Step 1: 初始化查找表a0 (a0 0.01) ? 0.01 : ((a0 0.99) ? 0.99 : a0); // 安全性检查// a0查找表, 进行了四舍五入ALUT LUT 256;for (ALUT[0] 0, dif 1; dif 256; dif){ALUT[dif] (int)(dif * a0 0.5);ALUT[-dif] (int)(-dif * a0 - 0.5);}// Step 2: 递推实现指数滤波// 按行滤波for (y 0, pGry pGryImg, pCur pTmpImg; y height; y){// 从左向右: p1(y,x) p1(y,x-1) a * [p(y,x) - p1(y,x-1)]*(pCur) pre *(pGry);for (x 1; x width; x, pGry)*(pCur) pre pre ALUT[*pGry - pre];pCur--; // 回到行尾// 从右向左: p2(y,x) p2(y,x1) - a * [p1(y,x) - p2(y,x1)]for (x width - 2, pCur pCur - 1; x 0; x--)*(pCur--) pre pre ALUT[*pCur - pre];pCur (width 1); // 回到下一行的开始}// 按列滤波for (x 0, pCur pTmpImg; x width; x, pCur pTmpImg x){// 从上向下: p3(y,x) p3(y-1,x) a * [p2(y,x) - p3(y-1,x)]pre *pCur;for (y 1, pCur width; y height; y, pCur width)*pCur pre pre ALUT[*pCur - pre];pCur - width; // 回到列尾// 从下向上: p4(i,j) p4(i1,j) a * [p3(i,j) - p4(i1,j)]for (y height - 2, pCur - width; y 0; y--, pCur - width)*pCur pre pre ALUT[*pCur - pre];}// Step 3: 正导数1负导数为00必须也是0pEnd pTmpImg width * height;for (pCur pTmpImg, pGry pGryImg; pCur pEnd; pGry){*(pCur) (*pCur *pGry);}// Step 4: 过零点检测memset(pSJImg, 0, width * height); // 边缘强度赋0pSJ pSJImg width;pCur pTmpImg width; // 首行不做 for (y 1; y height - 1; y){pSJ; pCur; // 首列不做for (x 1; x width - 1; x, pGry, pCur, pSJ){if (*pCur) // 正导数{// 下面使用4邻域, 边缘为8连通, 不能保证4连通; 使用8邻域才能保证边缘4连通if ((!*(pCur - 1)) || // 左, 必须0, 不能0(!*(pCur 1)) || // 右, 必须0, 不能0(!*(pCur - width)) || // 上, 必须0, 不能0(!*(pCur width))) // 下, 必须0, 不能0{*pSJ 255; // 周围有导数小于等于0}}}pSJ; pCur; // 尾列不做}
}当滤波系数为0.1时我们来看效果 沈俊算子能够得到闭合的边缘。一种边缘检测方法能够得到一般要用图像分割才能得到的目标轮廓会具有很高的实用价值。 沈俊算子只需要一个参数a_0且a_0语义明确而且沈俊算子的代码量非常小所以沈俊算子使用起来非常方便。 测试
那么该如何更好的得到我们原始图像的边缘呢
可能一个边缘处理都没有办法得到很好的效果我们可以结合多个处理办法 我们可以看到效果比较好的是索贝尔算子那么我们结合一下
//沈俊算子加索贝尔算子
//pGryImg指向原始灰度图像数据的指针
//pTmpImg指向辅助图像数据的指针
//width图像的宽度
//height图像的高度
//a0这是沈俊算子的参数用于控制边缘检测的灵敏度。
//grdThre这是Sobel算子的梯度阈值
//pEdgeImg最终边缘图像数据的指针
void RmwExtractRiceEdge(uint8_t* pGryImg,uint8_t* pTmpImg,int width,int height,double a0, int grdThre, uint8_t* pEdgeImg)
{// step.1------------沈俊算子-----------------------//RmwShenJunGryImg(pGryImg, pTmpImg, width, height, a0, pEdgeImg);// step.2------------Sobel算子----------------------//RmwSobelGryImg(pGryImg, width, height, pTmpImg);// step.3------------二者融合-----------------------//for (int i 0; i width * height; i){*(pEdgeImg i) (pEdgeImg[i] (pTmpImg[i] grdThre)) * 255;}// step.4------------结束---------------------------//return;
}我们换一张图片试一下 总结
那么到目前为止我们已经可以成功的进行边缘检测
接下来就是边缘增强边缘分割等相关内容会尽快更新
源码
IDP.h
#pragma once#include stdio.h
#include stdint.h
#include stdlib.h
#include math.h
#include nmmintrin.huint8_t* readGrayScaleBMP(const char* filename, int* width, int* height);//读取8位灰度图片
void saveGrayScaleBMP(const char* filename, const uint8_t* imageData, int width, int height);// 将8位灰度图像数据保存为BMP文件
uint8_t* readColorBMP(const char* filename, int* width, int* height);//读取24位彩色图像的BMP文件
void saveColorBMP(const char* filename, const uint8_t* imageData, int width, int height);//将24位彩色图像数据保存为BMP文件
void convertToGray(uint8_t* rgbImage, uint8_t* grayImage, int width, int height);//24位彩色图像转8位灰度值
void LinearStretchDemo(uint8_t* pGryImg, int width, int height, double k, double b);//灰度线性拉伸
void GetHistogram(uint8_t* pImg, int width, int height, int* histogram);//统计图像灰度值
void GetBrightContrast(int* histogram, double* bright, double* contrast);//亮度和对比度
void RmwHistogramEqualize(uint8_t* pGryImg, int width, int height);//直方图均衡化
void RmwLogTransform(uint8_t* pGryImg, int width, int height);//对数变换
void RmwAvrFilterBySumCol(uint8_t* pGryImg, int width, int height, int M, int N, uint8_t* pResImg);//基于列积分的快速均值滤波
void RmwDoSumGryImg(uint8_t* pGryImg, int width, int height, int* pSumImg);//基于列积分的积分图实现
void RmwDoSumGryImg_SSE(uint8_t* pGryImg, int width, int height, int* pSumImg);//基于SSE的积分图实现
void RmwAvrFilterBySumImg(int* pSumImg, int width, int height, int M, int N, uint8_t* pResImg);//基于积分图的快速均值滤波
void GetMedianGry(int* histogram, int N, int* medGry);//求灰度值中值
double RmwMedianFilter(uint8_t* pGryImg, int width, int height, int M, int N, uint8_t* pResImg);//中值滤波
void RmwBinImgFilter(uint8_t* pBinImg, int width, int height, int M, int N, double threshold, uint8_t* pResImg);//二值滤波
void RmwGradientGryImg(uint8_t* pGryImg, int width, int height, uint8_t* pGrdImg);//梯度算子
void RmwGradientGryImgPlus(uint8_t* pGryImg, int width, int height, uint8_t* pGrdImg, int threshold);//梯度算子加阈值
void invertImage(uint8_t* image, int width, int height);//反相
void RmwRobertsGryImg(uint8_t* pGryImg, int width, int height, uint8_t* pRbtImg);//罗伯特算子
void RmwSobelGryImg(uint8_t* pGryImg, int width, int height, uint8_t* pSbImg);//索贝尔算子
void RmwPrewittGryImg(uint8_t* pGryImg, int width, int height, uint8_t* pPRTImg); //Prewitt算子
void RmwShenJunGryImg(uint8_t* pGryImg, uint8_t* pTmpImg, int width, int height, double a0, uint8_t* pSJImg);//沈俊算子
void RmwExtractRiceEdge(uint8_t* pGryImg, uint8_t* pTmpImg, int width, int height, double a0, int grdThre, uint8_t* pEdgeImg);//索贝尔沈俊算子IDP.c
#define _CRT_SECURE_NO_WARNINGS 1#include IDP.h//读取8位灰度图片
//filename字符数组的指针用于指定要保存的图像文件的名称或路径。
//imageData无符号 8 位整型数据的指针代表要保存的图像数据。
//width图像的宽度。
//height图像的高度。
uint8_t* readGrayScaleBMP(const char* filename, int* width, int* height)
{FILE* file fopen(filename, rb);if (!file) {fprintf(stderr, Error opening file %s\n, filename);return NULL;}// 读取BMP文件头部信息uint8_t bmpHeader[54];fread(bmpHeader, 1, 54, file);// 从文件头部提取图像宽度和高度信息*width *(int*)bmpHeader[18];*height *(int*)bmpHeader[22];// 分配存储图像数据的内存uint8_t* imageData (uint8_t*)malloc(*width * *height);if (!imageData) {fprintf(stderr, 内存分配失败\n);fclose(file);return NULL;}// 计算调色板的大小int paletteSize *(int*)bmpHeader[46];if (paletteSize 0)paletteSize 256;// 读取调色板数据uint8_t palette[1024];fread(palette, 1, paletteSize * 4, file);// 读取图像数据fseek(file, *(int*)bmpHeader[10], SEEK_SET);fread(imageData, 1, *width * *height, file);fclose(file);return imageData;
}// 将8位灰度图像数据保存为BMP文件
//filename字符数组的指针用于指定要保存的图像文件的名称或路径。
//imageData无符号 8 位整型数据的指针代表要保存的图像数据。
//width图像的宽度。
//height图像的高度。
void saveGrayScaleBMP(const char* filename, const uint8_t* imageData, int width, int height)
{FILE* file fopen(filename, wb);if (!file) {fprintf(stderr, Error creating file %s\n, filename);return;}// BMP文件头部信息uint8_t bmpHeader[54] {0x42, 0x4D, // 文件类型标识 BM0x36, 0x00, 0x0C, 0x00, // 文件大小以字节为单位此处假设图像数据大小不超过4GB0x00, 0x00, // 保留字段0x00, 0x00, // 保留字段0x36, 0x00, 0x00, 0x00, // 位图数据偏移以字节为单位0x28, 0x00, 0x00, 0x00, // 位图信息头大小40字节0x00, 0x00, 0x00, 0x00, // 图像宽度0x00, 0x00, 0x00, 0x00, // 图像高度0x01, 0x00, // 目标设备的级别此处为1不压缩0x08, 0x00, // 每个像素的位数8位0x00, 0x00, 0x00, 0x00, // 压缩类型此处为不压缩0x00, 0x00, 0x00, 0x00, // 图像数据大小以字节为单位此处为0表示不压缩0x00, 0x00, 0x00, 0x00, // 水平分辨率像素/米此处为0表示未知0x00, 0x00, 0x00, 0x00, // 垂直分辨率像素/米此处为0表示未知0x00, 0x00, 0x00, 0x00, // 使用的颜色索引数0表示使用所有调色板项0x00, 0x00, 0x00, 0x00 // 重要的颜色索引数0表示所有颜色都重要};// 更新BMP文件头部信息中的宽度和高度*(int*)bmpHeader[18] width;*(int*)bmpHeader[22] height;// 写入BMP文件头部信息fwrite(bmpHeader, 1, 54, file);// 写入调色板数据for (int i 0; i 256; i) {fputc(i, file); // 蓝色分量fputc(i, file); // 绿色分量fputc(i, file); // 红色分量fputc(0, file); // 保留字节}// 写入图像数据fwrite(imageData, 1, width * height, file);fclose(file);
}// 读取24位彩色图像的BMP文件
//filename字符数组的指针用于指定要读取的 BMP 格式图像文件的名称或路径。
//width整型变量的指针用于存储读取的图像的宽度。
//height整型变量的指针用于存储读取的图像的高度。
uint8_t* readColorBMP(const char* filename, int* width, int* height)
{FILE* file fopen(filename, rb);if (!file) {fprintf(stderr, Error opening file %s\n, filename);return NULL;}// 读取BMP文件头部信息uint8_t bmpHeader[54];fread(bmpHeader, 1, 54, file);// 从文件头部提取图像宽度和高度信息*width *(int*)bmpHeader[18];*height *(int*)bmpHeader[22];// 分配存储图像数据的内存uint8_t* imageData (uint8_t*)malloc(*width * *height * 3);if (!imageData) {fprintf(stderr, Memory allocation failed\n);fclose(file);return NULL;}// 读取图像数据fseek(file, *(int*)bmpHeader[10], SEEK_SET);fread(imageData, 1, *width * *height * 3, file);fclose(file);return imageData;
}//将24位彩色图像数据保存为BMP文件
//filename字符数组的指针用于指定要保存的图像文件的名称或路径。
//imageData无符号 8 位整型数据的指针代表要保存的图像数据。
//width图像的宽度。
//height图像的高度。
void saveColorBMP(const char* filename, const uint8_t* imageData, int width, int height)
{FILE* file fopen(filename, wb);if (!file) {fprintf(stderr, Error creating file %s\n, filename);return;}// BMP文件头部信息uint8_t bmpHeader[54] {0x42, 0x4D, // 文件类型标识 BM0x00, 0x00, 0x00, 0x00, // 文件大小占位稍后计算0x00, 0x00, // 保留字段0x00, 0x00, // 保留字段0x36, 0x00, 0x00, 0x00, // 位图数据偏移以字节为单位0x28, 0x00, 0x00, 0x00, // 位图信息头大小40字节0x00, 0x00, 0x00, 0x00, // 图像宽度0x00, 0x00, 0x00, 0x00, // 图像高度0x01, 0x00, // 目标设备的级别此处为1不压缩0x18, 0x00, // 每个像素的位数24位0x00, 0x00, 0x00, 0x00, // 压缩类型此处为不压缩0x00, 0x00, 0x00, 0x00, // 图像数据大小占位稍后计算0x00, 0x00, 0x00, 0x00, // 水平分辨率像素/米此处为0表示未知0x00, 0x00, 0x00, 0x00, // 垂直分辨率像素/米此处为0表示未知0x00, 0x00, 0x00, 0x00, // 使用的颜色索引数0表示使用所有调色板项0x00, 0x00, 0x00, 0x00 // 重要的颜色索引数0表示所有颜色都重要};// 更新BMP文件头部信息中的宽度和高度*(int*)bmpHeader[18] width;*(int*)bmpHeader[22] height;// 计算图像数据大小uint32_t imageDataSize width * height * 3 54; // 加上文件头部大小bmpHeader[2] (uint8_t)(imageDataSize 0xFF);bmpHeader[3] (uint8_t)((imageDataSize 8) 0xFF);bmpHeader[4] (uint8_t)((imageDataSize 16) 0xFF);bmpHeader[5] (uint8_t)((imageDataSize 24) 0xFF);// 写入BMP文件头部信息fwrite(bmpHeader, 1, 54, file);// 写入图像数据fwrite(imageData, width * height * 3, 1, file);fclose(file);
}//24位彩色图像转8位灰度值
//rgbImage原始图像
//grayImage输出灰度图像
//width,height图片的宽和高
void convertToGray(uint8_t* rgbImage, uint8_t* grayImage, int width, int height)
{for (int y 0; y height; y) {for (int x 0; x width; x) {// 获取当前像素的 RGB 分量uint8_t r rgbImage[3 * (y * width x) 0];uint8_t g rgbImage[3 * (y * width x) 1];uint8_t b rgbImage[3 * (y * width x) 2];// 计算灰度值常用的加权平均法// 这里使用的加权系数是常见的R: 0.299, G: 0.587, B: 0.114uint8_t gray (uint8_t)(0.299 * r 0.587 * g 0.114 * b);// 将灰度值写入灰度图像数组grayImage[y * width x] gray;}}
}//灰度线性拉伸
//pGryImg灰度图像数据的指针。
//width图像的宽度。
//height图像的高度。
//k线性拉伸的斜率。它控制着拉伸的速率或程度。当(k) 大于 1 时图像的对比度增加当(k) 小于 1 时对比度降低。
//b线性拉伸的偏移。它控制着拉伸后灰度值的起始位置。当(b) 大于 0 时图像的整体亮度增加当(b) 小于 0 时整体亮度减小。
void LinearStretchDemo(uint8_t* pGryImg, int width, int height, double k, double b)
{uint8_t* pCur, * pEnd;int LUT[256]; //因为只有[0,255]共256个灰度值//step1. 生成查找表for (int g 0; g 256; g){LUT[g] max(0, min(255, k * g b));}//step2. 进行变换for (pCur pGryImg, pEnd pGryImg width * height; pCur pEnd; pCur){*pCur LUT[*pCur];}//step3. 结束return;
}//统计图像灰度值
//pImg灰度图像数据的指针。
//width图像的宽度。
//height图像的高度。
//* histogram数组首元素地址需要一个能储存256个变量的整型数组
void GetHistogram(uint8_t* pImg, int width, int height, int* histogram)
{uint8_t* pCur;uint8_t* pEnd pImg width * height;// 初始化直方图数组memset(histogram, 0, sizeof(int) * 256);// 直方图统计for (pCur pImg; pCur pEnd;){histogram[*pCur];pCur;}// 函数结束return;
}//亮度和对比度
//储存histogram灰度直方图的指针
//接收亮度的变量地址
//接收对比度的变量地址
void GetBrightContrast(int* histogram, double* bright, double* contrast)
{int g;double sum, num; //书上说图像很亮时int有可能会溢出所以我这里直接用doubledouble fsum;//step.1 求亮度for (sum num 0, g 0; g 256; g){sum histogram[g] * g;num histogram[g];}*bright sum * 1.0 / num;//step.2 求对比度for (fsum 0.0, g 0; g 256; g){fsum histogram[g] * (g - *bright) * (g - *bright);}*contrast sqrt(fsum / (num - 1)); //即Std Dev//step.3 结束return;
}//pGryImg灰度图像数据的指针。
//width图像的宽度。
//height图像的高度。
void RmwHistogramEqualize(uint8_t* pGryImg, int width, int height)
{uint8_t* pCur, * pEnd pGryImg width * height; // 指针变量指向当前像素和图像末尾int histogram[256], LUT[256], A, g; // 直方图数组、查找表数组、累积直方图、灰度级// step.1-------------求直方图--------------------------//memset(histogram, 0, sizeof(int) * 256); // 初始化直方图数组为0for (pCur pGryImg; pCur pEnd;)histogram[*(pCur)]; // 统计每个灰度级出现的频率// step.2-------------求LUT[g]-------------------------//A histogram[0]; // 初始化累积直方图的值为第一个灰度级的频率LUT[0] 255 * A / (width * height); // 计算第一个灰度级对应的均衡化后的灰度值for (g 1; g 256; g) {A histogram[g]; // 更新累积直方图的值LUT[g] 255 * A / (width * height); // 计算当前灰度级对应的均衡化后的灰度值}// step.3-------------查表------------------------------//for (pCur pGryImg; pCur pEnd;)*(pCur) LUT[*pCur]; // 使用查找表对每个像素进行灰度映射// step.4-------------结束------------------------------//return;
}//对数变换
//pGryImg灰度图像数据的指针。
//width图像的宽度。
//height图像的高度。
void RmwLogTransform(uint8_t* pGryImg, int width, int height)
{uint8_t* pCur, * pEnd pGryImg width * height; // 指向灰度图像数据的当前指针和结束指针int histogram[256], LUT[256], gmax, g; // 声明直方图数组、查找表数组、最大灰度值、当前灰度值double c; // 声明常数c// step.1-------------求直方图--------------------------//memset(histogram, 0, sizeof(int) * 256); // 初始化直方图数组为0for (pCur pGryImg; pCur pEnd;)histogram[*(pCur)]; // 遍历图像数据统计每个灰度级的像素数量// step.2-------------最大值---------------------------//for (gmax 255; gmax 0; gmax)if (histogram[gmax]) break; // 从最大灰度级开始向低灰度级搜索找到第一个非零灰度级即最大灰度值// step.3-------------求LUT[g]-------------------------//c 255.0 / log(1 gmax); // 计算常数cfor (g 0; g 256; g){LUT[g] (int)(c * log(1 g)); // 根据对数变换公式计算查找表中每个灰度级的映射值}// step.4-------------查表------------------------------//for (pCur pGryImg; pCur pEnd;)*(pCur) LUT[*pCur]; // 使用查找表将图像数据进行对数变换// step.5-------------结束------------------------------//return; // 函数结束
}//基于列积分的快速均值滤波
//原始灰度图像
//图像的宽度和高度
//滤波邻域M列N行
//结果图像
void RmwAvrFilterBySumCol(uint8_t* pGryImg,int width, int height,int M, int N,uint8_t* pResImg)
{uint8_t* pAdd, * pDel, * pRes;int halfx, halfy;int x, y;int sum, c;int sumCol[4096]; // 约定图像宽度不大于4096// step.1------------初始化--------------------------//M M / 2 * 2 1; // 奇数化N N / 2 * 2 1; // 奇数化halfx M / 2; // 滤波器的半径xhalfy N / 2; // 滤波器的半径yc (1 23) / (M * N); // 乘法因子memset(sumCol, 0, sizeof(int) * width);for (y 0, pAdd pGryImg; y N; y) {for (x 0; x width; x) sumCol[x] *(pAdd);}// step.2------------滤波----------------------------//for (y halfy, pRes pResImg y * width, pDel pGryImg; y height - halfy; y) {// 初值for (sum 0, x 0; x M; x) sum sumCol[x];// 滤波pRes halfx; // 跳过左侧for (x halfx; x width - halfx; x) {// 求灰度均值// *(pRes)sum/(N*M);*(pRes) (sum * c) 23; // 用整数乘法和移位代替除法// 换列,更新灰度和sum - sumCol[x - halfx]; // 减左边列sum sumCol[x halfx 1]; // 加右边列}pRes halfx; // 跳过右侧// 换行,更新sumColfor (x 0; x width; x) {sumCol[x] - *(pDel); // 减上一行sumCol[x] *(pAdd); // 加下一行}}// step.3------------返回----------------------------//return;
}//基于列积分的积分图实现
//pGryImg, // 原始灰度图像
//width, // 图像的宽度
//height, // 图像的高度
//pSumImg // 计算得到的积分图
void RmwDoSumGryImg(uint8_t* pGryImg,int width,int height, int* pSumImg)
{uint8_t* pGry;int* pRes;int x, y;int sumCol[4096]; // 约定图像宽度不大于4096memset(sumCol, 0, sizeof(int) * width);for (y 0, pGry pGryImg, pRes pSumImg; y height; y){// 最左侧像素的特别处理sumCol[0] *(pGry);*(pRes) sumCol[0];// 正常处理for (x 1; x width; x){sumCol[x] *(pGry); // 更新列积分int temp *(pRes - 1);*(pRes) temp sumCol[x];}}return;
}//基于SSE的积分图实现
//pGryImg原始灰度图像
//width图像的宽度必须是4的倍数
//height图像的高度
//pSumImg计算得到的积分图
void RmwDoSumGryImg_SSE(uint8_t* pGryImg,int width,int height,int* pSumImg)
{int sumCol[4096]; //约定图像宽度不大于4096__m128i* pSumSSE, A;uint8_t* pGry;int* pRes;int x, y;memset(sumCol, 0, sizeof(int) * width);for (y 0, pGry pGryImg, pRes pSumImg; y height; y){// 0:需要特别处理sumCol[0] *(pGry);*(pRes) sumCol[0];// 1sumCol[1] *(pGry);*(pRes) *(pRes - 1) sumCol[1];// 2sumCol[2] *(pGry);*(pRes) *(pRes - 1) sumCol[2];// 3sumCol[3] *(pGry);*(pRes) *(pRes - 1) sumCol[3];// [4...width-1]for (x 4, pSumSSE (__m128i*)(sumCol 4); x width; x 4, pGry 4){// 把变量的低32位(有4个8位整数组成)转换成32位的整数A _mm_cvtepu8_epi32(_mm_loadl_epi64((__m128i*)pGry));// 4个32位的整数相加*(pSumSSE) _mm_add_epi32(*pSumSSE, A);// 递推*(pRes) *(pRes - 1) sumCol[x 0];*(pRes) *(pRes - 1) sumCol[x 1];*(pRes) *(pRes - 1) sumCol[x 2];*(pRes) *(pRes - 1) sumCol[x 3];}}return;
}//基于积分图的快速均值滤波
//pSumImg计算得到的积分图
//width,height,图像的宽度和高度
//M, N,滤波邻域M列N行
//pResImg 结果图像
void RmwAvrFilterBySumImg(int* pSumImg,int width, int height,int M, int N,uint8_t* pResImg)
{// 没有对边界上邻域不完整的像素进行处理可以采用变窗口的策略int* pY1, * pY2;uint8_t* pRes;int halfx, halfy;int y, x1, x2;int sum, c;// step.1------------初始化--------------------------//M M / 2 * 2 1; // 奇数化N N / 2 * 2 1; // 奇数化halfx M / 2; // 滤波器的半径xhalfy N / 2; // 滤波器的半径yc (1 23) / (M * N); // 乘法因子// step.2------------滤波----------------------------//for (y halfy 1, pRes pResImg y * width, pY1 pSumImg, pY2 pSumImg N * width;y height - halfy;y, pY1 width, pY2 width){pRes halfx 1; // 跳过左侧for (x1 0, x2 M; x2 width; x1, x2) // 可以简化如此但不太容易读{sum *(pY2 x2) - *(pY2 x1) - *(pY1 x2) *(pY1 x1);*(pRes) (uint8_t)((sum * c) 23); // 用整数乘法和移位代替除法}pRes halfx; // 跳过右侧}// step.3------------返回----------------------------//return;
}void GetMedianGry(int* histogram, int N, int* medGry)
{int g;int num;// step.1-------------求灰度中值------------------------//num 0;for (g 0; g 256; g){num histogram[g];if (2 * num N) break; //numN/2}*medGry g;// step.2-------------结束------------------------------//return;
}//中值滤波
//pGryImg指向待处理灰度图像数据的指针。
//width、height表示图像的宽度和高度。
//M、N分别表示中值滤波器的水平和垂直邻域大小以像素为单位。
//pResImg指向存储结果图像数据的指针。
double RmwMedianFilter(uint8_t* pGryImg, int width, int height, int M, int N, uint8_t* pResImg)
{uint8_t* pCur, * pRes;int halfx, halfy, x, y, i, j, y1, y2;int histogram[256];int wSize, j1, j2;int num, med, v;int dbgCmpTimes 0; // 搜索中值所需比较次数的调试M M / 2 * 2 1; // 奇数化N N / 2 * 2 1; // 奇数化halfx M / 2; // x半径halfy N / 2; // y半径wSize (halfx * 2 1) * (halfy * 2 1); // 邻域内像素总个数for (y halfy, pRes pResImg y * width; y height - halfy; y) {// step.1----初始化直方图y1 y - halfy;y2 y halfy;memset(histogram, 0, sizeof(int) * 256);for (i y1, pCur pGryImg i * width; i y2; i, pCur width) {for (j 0; j halfx * 2 1; j) {histogram[*(pCur j)];}}// step.2-----初始化中值num 0; // 记录着灰度值从0到中值的个数for (i 0; i 256; i) {num histogram[i];if (num * 2 wSize) {med i;break;}}// 滤波pRes halfx; // 没有处理图像左边界侧的像素for (x halfx; x width - halfx; x) {// 赋值*(pRes) med;// step.3-----直方图递推: 减去当前邻域最左边的一列,添加邻域右侧的一个新列j1 x - halfx; // 最左边列j2 x halfx 1; // 右边的新列for (i y1, pCur pGryImg i * width; i y2; i, pCur width) {// 减去最左边列v *(pCur j1);histogram[v]--; // 更新直方图if (v med) num--; // 更新num// 添加右边的新列v *(pCur j2);histogram[v]; // 更新直方图if (v med) num; // 更新num}// step.4-----更新中值if (num * 2 wSize) { // 到上次中值med的个数不够了,则med要变大for (med med 1; med 256; med) {dbgCmpTimes 2; // 总的比较次数,调试用num histogram[med];if (num * 2 wSize) break;}dbgCmpTimes 1; // 总的比较次数,调试用}else { // 到上次中值med的个数多了,则med要变小while ((num - histogram[med]) * 2 wSize) { // 若减去后,仍变小dbgCmpTimes; // 总的比较次数,调试用num - histogram[med];med--;}dbgCmpTimes 2; // 总的比较次数,调试用}}pRes halfx; // 没有处理图像右边界侧的像素}// 返回搜索中值需要的平均比较次数return dbgCmpTimes * 1.0 / ((width - halfx * 2) * (height - halfy * 2));
}//二值滤波
//pBinImg, 原始二值图像
// width, height,图像的宽度和高度
// M, N, 滤波邻域M列N行
// threshold, 灰度阈值,大于等于该值时结果赋255
// pResImg 结果图像
void RmwBinImgFilter(uint8_t* pBinImg,int width, int height,int M, int N,double threshold,uint8_t* pResImg )
{// 没有对边界上邻域不完整的像素进行处理可以采用变窗口的策略uint8_t* pAdd, * pDel, * pRes;int halfx, halfy;int x, y, sum, sumThreshold;int sumCol[4096]; //约定图像宽度不大于4096// step.1------------初始化--------------------------//M M / 2 * 2 1; //奇数化N N / 2 * 2 1; //奇数化halfx M / 2; //滤波器的x半径halfy N / 2; //滤波器的y半径sumThreshold max(1, (int)(threshold * M * N)); //转换成邻域内灰度值之和的阈值memset(sumCol, 0, sizeof(int) * width);for (y 0, pAdd pBinImg; y N; y){for (x 0; x width; x)sumCol[x] *(pAdd);}// step.2------------滤波----------------------------//for (y halfy, pRes pResImg y * width, pDel pBinImg; y height - halfy; y){//初值for (sum 0, x 0; x M; x)sum sumCol[x];//滤波pRes halfx; //跳过左侧for (x halfx; x width - halfx; x){//求灰度均值/*if (sumsumThreshold){*(pRes) 255;}else *(pRes) 0;*/*(pRes) (sum sumThreshold) * 255; //请理解这个表达式的含义//换列,更新灰度和sum - sumCol[x - halfx]; //减左边列sum sumCol[x halfx 1]; //加右边列}pRes halfx; //跳过右侧//换行,更新sumColfor (x 0; x width; x){sumCol[x] - *(pDel); //减上一行sumCol[x] *(pAdd); //加下一行}}// step.3------------返回----------------------------//return;
}//梯度算子加阈值
//pGryImg输入的灰度图像数据指针。
//width图像的宽度。
//height图像的高度。
//pGrdImg输出的梯度图像数据指针
void RmwGradientGryImg(uint8_t* pGryImg, int width, int height, uint8_t* pGrdImg)
{uint8_t* pGry, * pGrd;int dx, dy;int x, y;for (y 0, pGry pGryImg, pGrd pGrdImg; y height - 1; y){for (x 0; x width - 1; x, pGry){dx *pGry - *(pGry 1);dy *pGry - *(pGry width);int gradient (int)(sqrt(dx * dx * 1.0 dy * dy));*pGrd (gradient 255) ? 255 : gradient;}*pGrd 0; //尾列不做,边缘强度赋0pGry;}memset(pGrd, 0, width); //尾行不做,边缘强度赋0
}//梯度算子加阈值
//pGryImg输入的灰度图像数据指针。
//width图像的宽度。
//height图像的高度。
//pGrdImg输出的梯度图像数据指针
void RmwGradientGryImgPlus(uint8_t* pGryImg, int width, int height, uint8_t* pGrdImg, int threshold)
{uint8_t* pGry, * pGrd;int dx, dy;int x, y;for (y 0, pGry pGryImg, pGrd pGrdImg; y height - 1; y){for (x 0; x width - 1; x, pGry){dx *pGry - *(pGry 1);dy *pGry - *(pGry width);int gradient (int)(sqrt(dx * dx * 1.0 dy * dy));*(pGrd) (gradient threshold) ? min(255, gradient) : 0;}*(pGrd) 0; //尾列不做,边缘强度赋0pGry;}memset(pGrd, 0, width); //尾行不做,边缘强度赋0
}//反相
void invertImage(uint8_t* image, int width, int height) {for (int i 0; i width * height; i) {image[i] 255 - image[i];}
}//罗伯特算子
void RmwRobertsGryImg(uint8_t* pGryImg, int width, int height, uint8_t* pRbtImg)
{uint8_t* pGry, * pRbt;int dx, dy;int x, y;for (y 0, pGry pGryImg, pRbt pRbtImg; y height - 1; y){for (x 0; x width - 1; x, pGry){dx *pGry - *(pGry width 1);dy *(pGry 1) - *(pGry width);*pRbt (uint8_t)(dx dy ? dx : dy); // 使用三目运算符选择较大的值}*pRbt 0; // 尾列不做, 边缘强度赋0pGry;}memset(pRbt, 0, width); // 尾行不做, 边缘强度赋0
}//索贝尔算子
void RmwSobelGryImg(uint8_t* pGryImg, int width, int height, uint8_t* pSbImg)
{uint8_t* pGry, * pSb;int dx, dy;int x, y;memset(pSbImg, 0, width); // 首行不做, 边缘强度赋0for (y 1, pGry pGryImg width, pSb pSbImg width; y height - 1; y){*pSb 0; // 首列不做, 边缘强度赋0pGry;for (x 1; x width - 1; x, pGry){// 求dxdx *(pGry - 1 - width) (*(pGry - 1) * 2) *(pGry - 1 width);dx - *(pGry 1 - width) (*(pGry 1) * 2) *(pGry 1 width);// 求dydy *(pGry - width - 1) (*(pGry - width) * 2) *(pGry - width 1);dy - *(pGry width - 1) (*(pGry width) * 2) *(pGry width 1);// 结果*pSb (uint8_t)min(255, abs(dx) abs(dy));}*pSb 0; // 尾列不做, 边缘强度赋0pGry;}memset(pSb, 0, width); // 尾行不做, 边缘强度赋0
}//Prewitt算子
void RmwPrewittGryImg(uint8_t* pGryImg, int width, int height, uint8_t* pPRTImg)
{uint8_t* pGry, * pPRT;int dx, dy, d45, d135, v1, v2;int x, y;memset(pPRTImg, 0, width); // 首行不做, 边缘强度赋0for (y 1, pGry pGryImg width, pPRT pPRTImg width; y height - 1; y){*pPRT 0; // 首列不做, 边缘强度赋0pGry;for (x 1; x width - 1; x, pGry){// 求dxdx *(pGry - 1 - width) *(pGry - 1) *(pGry - 1 width);dx - *(pGry 1 - width) *(pGry 1) *(pGry 1 width);// 求dydy *(pGry - width - 1) *(pGry - width) *(pGry - width 1);dy - *(pGry width - 1) *(pGry width) *(pGry width 1);// 求45度d45 *(pGry - width - 1) *(pGry - width) *(pGry - 1);d45 - *(pGry width 1) *(pGry width) *(pGry 1);// 求135度d135 *(pGry - width) *(pGry - width 1) *(pGry 1);d135 - *(pGry width - 1) *(pGry width) *(pGry - 1);// 结果v1 abs(dx) abs(dy) ? abs(dx) : abs(dy);v2 abs(d45) abs(d135) ? abs(d45) : abs(d135);*pPRT (uint8_t)((v1 v2) ? ((v1 255) ? 255 : v1) : ((v2 255) ? 255 : v2));}*pPRT 0; // 尾列不做, 边缘强度赋0pGry;}memset(pPRT, 0, width); // 尾行不做, 边缘强度赋0
}//沈俊算子
//pGryImg 和 pTmpImg 是指向 uint8_t 类型的指针它们分别指向原始灰度图像数据和辅助图像数据。
//width 和 height 是整型参数表示图像的宽度和高度。
//a0 是双精度浮点型参数表示滤波系数。
//pSJImg 是指向 uint8_t 类型的指针它指向了输出的图像数据。
void RmwShenJunGryImg(uint8_t* pGryImg,uint8_t* pTmpImg, int width, int height, double a0, uint8_t* pSJImg)
{uint8_t* pGry, * pCur, * pSJ, * pEnd;int LUT[512], * ALUT; // a0查找表int x, y, pre, dif;// Step 1: 初始化查找表a0 (a0 0.01) ? 0.01 : ((a0 0.99) ? 0.99 : a0); // 安全性检查// a0查找表, 进行了四舍五入ALUT LUT 256;for (ALUT[0] 0, dif 1; dif 256; dif){ALUT[dif] (int)(dif * a0 0.5);ALUT[-dif] (int)(-dif * a0 - 0.5);}// Step 2: 递推实现指数滤波// 按行滤波for (y 0, pGry pGryImg, pCur pTmpImg; y height; y){// 从左向右: p1(y,x) p1(y,x-1) a * [p(y,x) - p1(y,x-1)]*(pCur) pre *(pGry);for (x 1; x width; x, pGry)*(pCur) pre pre ALUT[*pGry - pre];pCur--; // 回到行尾// 从右向左: p2(y,x) p2(y,x1) - a * [p1(y,x) - p2(y,x1)]for (x width - 2, pCur pCur - 1; x 0; x--)*(pCur--) pre pre ALUT[*pCur - pre];pCur (width 1); // 回到下一行的开始}// 按列滤波for (x 0, pCur pTmpImg; x width; x, pCur pTmpImg x){// 从上向下: p3(y,x) p3(y-1,x) a * [p2(y,x) - p3(y-1,x)]pre *pCur;for (y 1, pCur width; y height; y, pCur width)*pCur pre pre ALUT[*pCur - pre];pCur - width; // 回到列尾// 从下向上: p4(i,j) p4(i1,j) a * [p3(i,j) - p4(i1,j)]for (y height - 2, pCur - width; y 0; y--, pCur - width)*pCur pre pre ALUT[*pCur - pre];}// Step 3: 正导数1负导数为00必须也是0pEnd pTmpImg width * height;for (pCur pTmpImg, pGry pGryImg; pCur pEnd; pGry){*(pCur) (*pCur *pGry);}// Step 4: 过零点检测memset(pSJImg, 0, width * height); // 边缘强度赋0pSJ pSJImg width;pCur pTmpImg width; // 首行不做 for (y 1; y height - 1; y){pSJ; pCur; // 首列不做for (x 1; x width - 1; x, pGry, pCur, pSJ){if (*pCur) // 正导数{// 下面使用4邻域, 边缘为8连通, 不能保证4连通; 使用8邻域才能保证边缘4连通if ((!*(pCur - 1)) || // 左, 必须0, 不能0(!*(pCur 1)) || // 右, 必须0, 不能0(!*(pCur - width)) || // 上, 必须0, 不能0(!*(pCur width))) // 下, 必须0, 不能0{*pSJ 255; // 周围有导数小于等于0}}}pSJ; pCur; // 尾列不做}
}//沈俊算子加索贝尔算子
//pGryImg指向原始灰度图像数据的指针
//pTmpImg指向辅助图像数据的指针
//width图像的宽度
//height图像的高度
//a0这是沈俊算子的参数用于控制边缘检测的灵敏度。
//grdThre这是Sobel算子的梯度阈值
//pEdgeImg最终边缘图像数据的指针
void RmwExtractRiceEdge(uint8_t* pGryImg,uint8_t* pTmpImg,int width,int height,double a0, int grdThre, uint8_t* pEdgeImg)
{// step.1------------沈俊算子-----------------------//RmwShenJunGryImg(pGryImg, pTmpImg, width, height, a0, pEdgeImg);// step.2------------Sobel算子----------------------//RmwSobelGryImg(pGryImg, width, height, pTmpImg);// step.3------------二者融合-----------------------//for (int i 0; i width * height; i){*(pEdgeImg i) (pEdgeImg[i] (pTmpImg[i] grdThre)) * 255;}// step.4------------结束---------------------------//return;
}