边缘检测与图像分割
1图像分割原理
图像分割的研究多年来一直受到人们的高度重视,至今提出了各种类型的分割算法。Pal把图像分割算法分成了6类:阈值分割,像素分割、深度图像分割、彩色图像分割,边缘检测和基于模糊集的方法。但是,该方法中,各个类别的内容是有重叠的。为了涵盖不断涌现的新方法,有的研究者将图像分割算法分为以下六类:并行边界分割技术、串行边界分割技术、并行区域分割技术、串行区域分割技术、结合特定理论工具的分割技术和特殊图像分割技术。而在较近的一篇综述中,更有学者将图像分割简单的分割数据驱动的分割和模型驱动的分割两类。下面将图像分割方法主要分以下几类:基于阈值的分割方法、基于区域的分割方法、基于边缘的分割方法、基于数学形态的分割方法以及基于特定理论的分割方法等,对其中主要的分别进行简要介绍。
1.1灰度阈值分割法
是一种最常用的并行区域技术,它是图像分割中应用数量最多的一类。阈值分割方法实际上是输入图像f到输出图像g的如下变换:
其中,T为阈值,对于物体的图像元素g(i,j)=l,对于背景的图像元素g(i,j)=0。
由此可见,阈值分割算法的关键是确定阈值,如果能确定一个合适的阈值就可准确地将图像分割开来。如果阈值选取过高,则过多的目标区域将被划分为背景,相反如果阈值选取过低,则过多的背景将被划分到目标区[7]。阈值确定后,将阈值与像素点的灰度值比较和像素分割可对各像素并行地进行,分割的结果直接给出图像区域。
阈值分割必须满足一个假设条件:图像的直方图具有较明显的双峰或多峰,并在谷底选择闭值。因此这种方法对目标和背景反差较大的图像进行分割的效果十分明显,而且总能用封闭、连通的边界定义不交叠的区域。
阈值分割法主要分为全局和局部两种,目前应用的闭值分割方法都是在此基础上发展起来的,比如最小误差法、最大相关法、最大嫡法、矩量保持法、Otsu最大类间方差法等,而应用最广泛的是Otsu最大类间方差法。
人们发展了各种各样的阈值处理技术,包括全局阈值、自适应阈值、最佳阈值等等。 全局阈值是指整幅图像使用同一个阈值做分割处理,适用于背景和前景有明显对比的图像。它是根据整幅图像确定的:T=T(f)。但是这种方法只考虑像素本身的灰度值,一般不考虑空间特征,因而对噪声很敏感。常用的全局阈值选取方法有利用图像灰度直方图的峰谷法、最小误差法、最大类间方差法、最大熵自动阈值法以及其它一些方法。在许多情况下,物体和背景的对比度在图像中的各处不是一样的,这时很难用一个统一的阈值将物体与背景分开。这时可以根据图像的局部特征分别采用不同的阈值进行分割。实际处理时,需要按照具体问题将图像分成若干子区域分别选择阈值,或者动态地根据一定的邻域范围选择每点处的阈值,进行图像分割。这时的阈值为自适应阈值。阈值的选择需要根据具体问题来确定,一般通过实验来确定。对于给定的图像,可以通过分析直方图的方法确定最佳的阈值,例如当直方图明显呈现双峰情况时,可以选择两个峰值的中点作为最佳阈值。
阈值分割的优点是计算简单、运算效率较高、速度快,在算法上容易实现,在重视运算效率的应用场合(如用于硬件实现),它得到了广泛应用。它对目标和背景对比度反差较大图像这种分割很有效, 而且总能用封闭、连通的边界定义不交叠的区域。但它不适用于多通道图像和特征值相关不大的图像,对图像中不存在明显灰度差异或各物体的灰度值范围有较大重叠的图像分割问题难以得到准确结果。另外由于阈值确定主要依赖于灰度直方图, 而很少考虑图像中像素的空间位置关系,因此当背景复杂,特别在是同一背景上重叠出现若干个研究目标时,或图像中噪声信号较多时,目标的灰度值与背景相差无几等情形下,容易丧失部分边界信息,按照固定的阈值进行分割所得到的结果就不准确,造成分割不完整,需要进一步的精确定位。
1.2基于区域的分割方法
区域生长和分裂合并法是两种典型的串行区域技术,其分割过程后续步骤的处理要根据前面步骤的结果进行判断而确定。
(1)区域生长
区域生长的基本思想是将具有相似性质的像素集合起来构成区域。具体先对每个需要分割的区域找一个种子像素作为生长的起点,然后将种子像素周围邻域中与种子像素有相同或相似性质的像素(根据某种事先确定的生长或相似准则来判定)合并到种子像素所在的区域中。将这些新像素当作新的种子像素继续进行上面的过程,直到再没有满足条件的像素可被包括进来。
区域生长需要选择一组能正确代表所需区域的种子像素,确定在生长过程中的相似性准则,制定让生长停止的条件或准则。相似性准则可以是灰度级、彩色、纹理、梯度等特性。选取的种子像素可以是单个像素,也可以是包含若干个像素的小区域。大部分区域生长准则使用图像的局部性质。生长准则可根据不同原则制定,而使用不同的生长准则会影响区域生长的过程。
区域生长法的优点是计算简单,对于较均匀的连通目标有较好的分割效果,对有复杂物体定义的复杂场景的分割或者对自然景物的分割等类似先验知识不足的图像分割, 效果均较理想,Wu H S等提出利用肺部癌细胞图像的均值、标准偏差构成的矢量作为细胞分割的特征, 提出的区域增长分割算法分割肺部癌细胞纹理图像, 取得较好结果[10]。它的缺点是需要人为确定种子点,虽然其抗噪性能优于边缘分割和直方图分割,但仍对噪声敏感,可能导致区域内有空洞;另外,它是一种串行算法,当目标较大时,分割速度较慢,因此在设计算法时,要尽量提高效率;而且在计算过程中引入的预定误差值选取不当时,还会引入误判,易受分析目标内部组织之间的重叠干扰影响。因此,基于区域生长的分割方法一般适合于边缘光滑、无重叠的细胞图象的分割。
(2)区域分裂合并
区域生长是从某个或者某些像素点出发,最后得到整个区域,进而实现目标提取。分裂合并差不多是区域生长的逆过程:从整个图像出发,不断分裂得到各个子区域,然后再把前景区域合并,实现目标提取。分裂合并的假设是对于一幅图像,前景区域由一些相互连通的像素组成的,因此,如果把一幅图像分裂到像素级,那么就可以判定该像素是否为前景像素。当所有像素点或者子区域完成判断以后,把前景区域或者像素合并就可得到前景目标。在这类方法中,最常用的方法是四叉树分解法。设R代表整个正方形图像区域,P代表逻辑谓词。基本分裂合并算法步骤如下:
① 对任一个区域,如果H(Ri)=FALSE就将其分裂成不重叠的四等份;
② 对相邻的两个区域Ri和Rj,它们也可以大小不同(即不在同一层),如果条件H(Ri∪Rj)=TRUE满足,就将它们合并起来。
③ 如果进一步的分裂或合并都不可能,则结束。
分裂合并法的关键是分裂合并准则的设计。这种方法对复杂图像的分割效果较好,但算法较复杂,计算量大,分裂还可能破坏区域的边界。
1.3基于边缘的分割方法
图像分割的一种重要途径是通过边缘检测,即检测灰度级或者结构具有突变的地方,表明一个区域的终结,也是另一个区域开始的地方。这种不连续性称为边缘。不同的图像灰度不同,边界处一般有明显的边缘,利用此特征可以分割图像。图像中边缘处像素的灰度值不连续,这种不连续性可通过求导数来检测到。对于阶跃状边缘,其位置对应一阶导数的极值点,对应二阶导数的过零点(零交叉点)。因此常用微分算子进行边缘检测[11]。
常用的一阶微分算子有Roberts算子、Prewitt算子和Sobel算子,二阶微分算子有Laplace算子和Kirsh算子等。在实际中各种微分算子常用小区域模板来表示,微分运算是利用模板和图像卷积来实现。这些算子对噪声敏感,只适合于噪声较小不太复杂的图像。由于边缘和噪声都是灰度不连续点,在频域均为高频分量,直接采用微分运算难以克服噪声的影响。因此用微分算子检测边缘前要对图像进行平滑滤波。
Roberts算子有利于对具有陡峭边缘的低噪声图像的分割;laplacian算子具有各向同性的特点;Roberts算子和laplacian算子在实施过程中大大增强了噪声,恶化了信噪比。Prewitt算子、Sobel算子等有利于对具有较多噪声且灰度渐变图像的分割。Log算子和Canny算子是具有平滑功能的二阶和一阶微分算子,边缘检测效果较好。其中Log算子是采用Laplacian算子求高斯函数的二阶导数,Canny算子是高斯函数的一阶导数,它在噪声抑制和边缘检测之间取得了较好的平衡。Marr算法对有较多噪声的图像具有平滑作用,且其边缘检测效果优于以上几种算子,但Marr算法在平滑的同时导致图像对比度下降[7]。Kirch算法利用对梯度图像适当的阈值进行二值化, 使得目标和背景像素点低于阈值, 而大多数边缘点高于阀值, 同时为了提高性能, 在该类算法中可引入分水岭算法以进行准确分割[1]。
Hough变换法利用图像全局特性而直接检测目标轮廓,将边缘像素连接起来组成区域封闭边界的一种常见方法。在预先知道区域形状的条件下,利用哈夫变换可以方便地得到边界曲线而将不连续的边界像素点连接起来。它的主要优点是受噪声和曲线间断的影响较小。
对于灰度变化复杂和细节较丰富图象,边缘检测算子均很难完全检测出边缘,而且一旦有噪声干扰时,上述算子直接处理效果更不理想。这一方法用来分割显微图象的例子不多,因为显微图象中的许多纹理或颗粒会掩盖真正的边缘,虽然可以通过有关算法改进,但效果并不太好。
拟合算子( 即参数模型匹配算法)原理:用边缘的参数模型对图像的局部灰度值进行拟合, 再在拟合的参数模型上进行边缘检测。优缺点:此类算子在检测边缘的同时, 还平滑了噪声, 对有较大噪声和高纹理细胞图像处理效果较好, 但由于参数模型记录着更多的边缘结构信息, 计算开销很大, 算法复杂, 而且对边缘类型要求较高。
以上三种方法中,基于边缘分割的最常见的问题是在没有边界的地方出现了边缘以及在实际存在边界的地方没有出现边界, 这是由图像噪声或图像中的不适合的信息造成的[24]。基于区域增长方法分割后的图像, 时常是由于参数的设置非最优性造成, 不是含有太多的区域就是含有过少的区域。阈值化是最简单的分割处理, 计算代价小, 速度快, 用一个亮度常量即阈值来分割物体和背景。
2. 图像分割的实现
我用了Roberts算子、Sobel算子和Kirsh算子边缘检测的方法但都由于亮度不均等因素对图像分割的效果不太好:
Sobel:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 | void sobel (unsignedchar* des, constunsignedchar* src, int width, int height) { for ( int y=0; y<height; y++) for ( int x=0; x<width; x++) des[y * width + x]=255; /* Now compute the convolution, scaling */ for ( int y=1; y<height-1; y++) for ( int x=1; x<width-1; x++) { double n = (src[(y+1)*width+x-1]+2*src[(y+1)*width+x]+src[(y+1)*width+x+1]) - (src[(y-1)*width+x-1]+2*src[(y-1)*width+x]+src[(y-1)*width+x+1]); double m = (src[(y-1)*width+x+1]+2*src[y*width+x+1]+src[(y+1)*width+x+1])- (src[(y-1)*width+x-1]+2*src[y*width+x-1]+src[(y+1)*width+x-1]); double k = ( int )( sqrt ( ( double )(n*n + m*m) )/4.0 ); des[y * width + x] = k; } thresh (des, width,height); } Roberts算子: void roberts(unsignedchar* des, constunsignedchar* src, int width, int height) { for ( int y=0; y<height; y++) for ( int x=0; x<width; x++) des[y * width + x]=255; /* Now compute the convolution, scaling */ for ( int y=1; y<height-1; y++) for ( int x=1; x<width-1; x++) { double n = src[y*width+x] - src[(y+1)*width+x+1]; double m = src[(y+1)*width+x] - src[y*width+x+1]; double k = abs (m)+ abs (n); des[y * width + x] = k; } thresh (des, width,height); } Kirsch算子: void kirsch(unsigned char * des, const unsigned char * src, int width, int height) { // TODO: Add your command handler code here //显示数值 long int i,j,Ns; static int nWeight[8][3][3]; //对一个静态整型数组赋初值,模板 double dGrad[8]; int nTmp[3][3],xx,yy; //每像素点的邻域值 nWeight[0][0][0] = -1 ; nWeight[0][0][1] = 0 ; nWeight[0][0][2] = 1 ; nWeight[0][1][0] = -2 ; nWeight[0][1][1] = 0 ; nWeight[0][1][2] = 2 ; nWeight[0][2][0] = -1 ; nWeight[0][2][1] = 0 ; nWeight[0][2][2] = 1 ; nWeight[1][0][0] = -1 ; nWeight[1][0][1] = -2 ; nWeight[1][0][2] = -1 ; nWeight[1][1][0] = 0 ; nWeight[1][1][1] = 0 ; nWeight[1][1][2] = 0 ; nWeight[1][2][0] = 1 ; nWeight[1][2][1] = 2 ; nWeight[1][2][2] = 1 ; //负号上下??? 已改成8个方向模板的值 nWeight[2][0][0] = 0 ; nWeight[2][0][1] = -1 ; nWeight[2][0][2] = -2 ; nWeight[2][1][0] = 1 ; nWeight[2][1][1] = 0 ; nWeight[2][1][2] = -1 ; nWeight[2][2][0] = 2 ; nWeight[2][2][1] = 1 ; nWeight[2][2][2] = 0 ; nWeight[3][0][0] = 1 ; nWeight[3][0][1] = 0 ; nWeight[3][0][2] = -1 ; nWeight[3][1][0] = 2 ; nWeight[3][1][1] = 0 ; nWeight[3][1][2] = -2 ; nWeight[3][2][0] = 1 ; nWeight[3][2][1] = 0 ; nWeight[3][2][2] = -1 ; nWeight[4][0][0] = 2 ; nWeight[4][0][1] = 1 ; nWeight[4][0][2] = 0 ; nWeight[4][1][0] = 1 ; nWeight[4][1][1] = 0 ; nWeight[4][1][2] = -1 ; nWeight[4][2][0] = 0 ; nWeight[4][2][1] = -1 ; nWeight[4][2][2] = -2 ; nWeight[5][0][0] = 1 ; nWeight[5][0][1] = 2 ; nWeight[5][0][2] = 1 ; nWeight[5][1][0] = 0 ; nWeight[5][1][1] = 0 ; nWeight[5][1][2] = 0 ; nWeight[5][2][0] = -1 ; nWeight[5][2][1] = -2 ; nWeight[5][2][2] = -1 ; nWeight[6][0][0] = 0 ; nWeight[6][0][1] = 1 ; nWeight[6][0][2] = 2 ; nWeight[6][1][0] = -1 ; nWeight[6][1][1] = 0 ; nWeight[6][1][2] = 1 ; nWeight[6][2][0] = -2 ; nWeight[6][2][1] = -1 ; nWeight[6][2][2] = 0 ; nWeight[7][0][0] = -2 ; nWeight[7][0][1] = -1 ; nWeight[7][0][2] = 0 ; nWeight[7][1][0] = -1 ; nWeight[7][1][1] = 0 ; nWeight[7][1][2] = 1 ; nWeight[7][2][0] = 0 ; nWeight[7][2][1] = -1 ; nWeight[7][2][2] = 2 ; //注意:每行的字节数必须是4的整数倍!!!先不考虑 Ns=height*width; unsigned char * kk = new unsigned char [width * height]; //开始变换 initiion for (i=0; i<height ; i++ ) //if(i==0)//tt change at 05.05.16 for (j=0 ; j<width ; j++ ) { des[i*width + j]=0; //*(pdGrad+y*nWidth+x) } for (i=1; i<height-1 ; i++ ) { for (j=1 ; j<width-1 ; j++ ) { dGrad[0] = 0 ; dGrad[1] = 0 ; dGrad[2] = 0 ; dGrad[3] = 0 ; dGrad[4] = 0 ; dGrad[5] = 0 ; dGrad[6] = 0 ; dGrad[7] = 0 ; // sobel算子需要的各点象素值 // 模板第一行 nTmp[0][0] = src[(i-1)*width + j - 1 ]; nTmp[0][1] = src[(i-1)*width + j ] ; nTmp[0][2] = src[(i-1)*width + j + 1 ] ; // 模板第二行 nTmp[1][0] = src[i*width + j - 1 ] ; nTmp[1][1] = src[i*width + j ] ; nTmp[1][2] = src[i*width + j + 1 ] ; // 模板第三行 nTmp[2][0] = src[(i+1)*width + j - 1 ] ; nTmp[2][1] = src[(i+1)*width + j ] ; nTmp[2][2] = src[(i+1)*width + j + 1 ] ; // 计算梯度 for (yy=0; yy<3; yy++) for (xx=0; xx<3; xx++) { dGrad[0] += nTmp[yy][xx] * nWeight[0][yy][xx] ; dGrad[1] += nTmp[yy][xx] * nWeight[1][yy][xx] ; dGrad[2] += nTmp[yy][xx] * nWeight[2][yy][xx] ; dGrad[3] += nTmp[yy][xx] * nWeight[3][yy][xx] ; dGrad[4] += nTmp[yy][xx] * nWeight[4][yy][xx] ; dGrad[5] += nTmp[yy][xx] * nWeight[5][yy][xx] ; dGrad[6] += nTmp[yy][xx] * nWeight[6][yy][xx] ; dGrad[7] += nTmp[yy][xx] * nWeight[7][yy][xx] ; } for (xx=1;xx<8;xx++) { if (dGrad[xx]>dGrad[0]) dGrad[0]=dGrad[xx]; } des[i*width + j]=dGrad[0]; // 梯度值写入src[i] } } //设定阈值 int th[5120],newth[5120],shuN,newN,flagyuzhi; //winframe=32,ii,jj,initpos; double thk,kmin,mvalue[8]; shuN=0; thk=0.5; for (i=0;i<Ns;i++) //每层的每个点 { if ((i>=width) && (i<(Ns-width))) //若是非边界点,则…… { if ((i%width!=0) && ((i+1)%width!=0)) { //每点做变换,首先求kirs(c)h算子 mvalue[0]= fabs ( double (des[i+1]+des[i+width+1]+des[i+width]+\ des[i+width-1]+des[i-1]-des[i-width-1]-\ des[i-width]-des[i-width+1])); mvalue[1]= fabs ( double (des[i+width+1]+des[i+width]+\ des[i+width-1]+des[i-1]+des[i-width-1]-\ des[i-width]-des[i-width+1]-des[i+1])); mvalue[2]= fabs ( double (des[i+width]+des[i+width-1]+des[i-1]+\ des[i-width-1]+des[i-width]-\ des[i-width+1]-des[i+1]-des[i+width+1])); mvalue[3]= fabs ( double (des[i+width-1]+des[i-1]+\ des[i-width-1]+des[i-width]+\ des[i-width+1]-des[i+1]-des[i+width+1]-\ des[i+width])); mvalue[4]= fabs ( double (des[i-1]+des[i-width-1]+\ des[i-width]+des[i-width+1]+des[i+1]-\ des[i+width+1]-des[i+width]-\ des[i+width-1])); mvalue[5]= fabs ( double (des[i-width-1]+des[i-width]+\ des[i-width+1]+des[i+1]+des[i+width+1]-\ des[i+width]-des[i+width-1]-des[i-1])); mvalue[6]= fabs ( double (des[i-width]+des[i-width+1]+des[i+1]+\ des[i+width+1]+des[i+width]-\ des[i+width-1]-des[i-1]-des[i-width-1])); mvalue[7]= fabs ( double (des[i-width+1]+des[i+1]+des[i+width+1]+\ des[i+width]+des[i+width-1]-\ des[i-1]-des[i-width-1]-des[i-width])); for (j=1;j<8;j++) //比较得出算子,mvalue[0]为最大 { if (mvalue[0]<mvalue[j]) mvalue[0]=mvalue[j]; } kk[i]=max(1,mvalue[0]/15); if (shuN==0) kmin=kk[i]; if (kk[i]>thk) { th[shuN]=i; kmin=min(kmin,kk[i]); shuN++; if (shuN>=5*height) //若大于5*H个点,则重新确定 { //AfxMessageBox("lll"); thk=kmin; newN=0; for (j=0;j<shuN;j++) { if (kk[th[j]]>thk) { if (newN==0) kmin=kk[th[j]]; newth[newN]=th[j]; kmin=min(kmin,kk[th[j]]); newN++; } //else des[th[j]]=0; } for (j=0;j<5120;j++) { th[j]=newth[j]; } shuN=newN; } //重新确定完 } //非边界的每点变换结束 } } } //一层结束 for (i=0;i<Ns;i++) //每层的每个点 { if (des[i]<thk) des[i]=0; } thresh (des, width,height); //菜单函数结束 } |
下面三图分别为sobel、Roberts、kerish边缘检测的结果:
之后打算用霍夫变换检测直线找矩形框,但是由于光照形成的噪点效果并不是很好,因此最后用自适应直方图均衡去除光照影响加自适应中值滤波再用投影法实现矩形框和数字的检测。具体如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 | int main() { IplImage* src = cvLoadImage( "dm5.bmp" ); IplImage* gray = cvCreateImage(cvGetSize(src), src->depth, 1); cvCvtColor(src,gray,CV_BGR2GRAY); //灰度化 int width = src->width; int height = src->height; IplImage* dst = cvCreateImage(cvGetSize(src), src->depth, gray->nChannels); IplImage* scr = cvCreateImage(cvGetSize(gray), gray->depth, gray->nChannels); cvSmooth(gray, gray, CV_MEDIAN, 3, 0, 0, 0); //中值滤波,消除小的噪声; cvSmooth(gray, gray, CV_GAUSSIAN, 9, gray->nChannels); //高斯滤波 cvCvtColor(src,scr,CV_BGR2GRAY); cvThreshold( gray, gray, 190, 255, CV_THRESH_BINARY); //二值化 int nChannels =gray->nChannels; cvNamedWindow( "origin" ,0); cvResizeWindow( "origin" , int (width/2), int (height/2)); cvShowImage( "origin" , src); unsigned char * img = new unsigned char [width * height ]; unsigned char * des = new unsigned char [width * height ]; unsigned char * gra = new unsigned char [width * height]; unsigned char * grt = new unsigned char [width * height]; img_data(gray, gra,width,height, nChannels); img_data(scr, img,width,height,nChannels); AHE(des, img, width, height,nChannels,10); //自适应直方图均衡 Projection( grt,gra,width,height); //投影检测表盘区域 img_extract(des,grt,width,height,1); //表盘区域还原 //kirsch(des,gra, width,height); data_img( scr, des, width, height, nChannels); cvNamedWindow( "表盘" ,0); cvResizeWindow( "表盘" , int (width/2), int (height/2)); cvShowImage( "表盘" , scr); cvThreshold(scr, scr, 100, 255, CV_THRESH_BINARY); //表盘区域二值化以查找数字 img_data(scr, img,width,height,nChannels); Adaptivemedianfilter(des, img, width, height, nChannels); //自适应中值滤波去噪 ImageDilation( img, des, width, height,nChannels,1); ImageErosion( des,img,width, height,nChannels,1); //经过一次膨胀腐蚀去噪 location(img, des, width, height); //找出数字所在区域 data_img( scr, img, width, height, nChannels); cvNamedWindow( "数字" ,0); cvResizeWindow( "数字" , int (width/2), int (height/2)); cvSaveImage( "123.bmp" ,scr); cvShowImage( "数字" , scr); data_img( gray,des, width, height, nChannels); cvNamedWindow( "erzhi" ,0); cvResizeWindow( "erzhi" , int (width/2), int (height/2)); cvShowImage( "erzhi" , gray); cvWaitKey(0); } /************************************************************************** 函数名:Projection 功 能:投影法找出矩形区域 输 入:目标图像des, 原图像 src,图像宽width, 高height 返回值:no *************************************************************************/ void Projection(unsigned char * des, const unsigned char * src, int width, int height) { int * h_sum = new int [height]; int * w_sum = new int [width]; int up=0; int below=height; int left=0; int right=width; for ( int y=0;y<height;y++) { for ( int x=0;x<width;x++) { des[y*width+x]=255; } } for ( int y=0;y<height;y++) { h_sum[y]=0; for ( int x=0;x<width;x++) { //printf("src %d",src[y*width+x]); h_sum[y]=h_sum[y]+src[y*width+x]; } //printf("%d行%d ",y,h_sum[y]); } for ( int y=height/2;y<height;y++) { if ((h_sum[y]-h_sum[height/2])>255*60) { below=y; break ; } } for ( int y=height/2;y>0;y--) { if ((h_sum[y]-h_sum[height/2])>255*60) { up=y; break ; } } for ( int x=0;x<width;x++) { w_sum[x]=0; for ( int y=up;y<below;y++) { w_sum[x]=w_sum[x]+src[y*width+x]; } //printf("%d列%d ",x,w_sum[x]); } int max_r=0; int max_l=0; for ( int x=width/2+100;x<width;x++) { if (w_sum[x]>max_r) { right=x; max_r=w_sum[x]; } } for ( int x=width/2-100;x>0;x--) { if (w_sum[x]>max_l) { left=x; max_l=w_sum[x]; } } for ( int y=up;y<below;y++) { for ( int x=left;x<right;x++) { des[y*width+x]=0; } } printf ( "up%d below%d left%d right%d" ,up, below,left, right); } void img_extract(unsigned char * des, const unsigned char * src, int width, int height, int nChannels) { for ( int y=0;y<height;y++) for ( int x=0;x<width;x++) if (src[y*width+x]!=0) { for ( int n = 0; n < nChannels; n++) { des[y * width * nChannels + x * nChannels + n ] = 255; } } } /************************************************************************ 函数名:location 功 能:投影法找出数字 输 入:目标图像des, 原图像 src,图像宽width, 高height 返回值:no **********************************************************************/ void location(unsigned char * des, const unsigned char * src, int width, int height) { int * h_sum = new int [height]; int * w_sum = new int [width]; int up=0; int below=height; int left=0; int right=width; for ( int y=0;y<height;y++) { for ( int x=0;x<width;x++) { des[y*width+x]=255; } } for ( int y=0;y<height;y++) { h_sum[y]=0; for ( int x=0;x<width;x++) { //printf("src %d",src[y*width+x]); h_sum[y]=h_sum[y]+src[y*width+x]; } //printf("%d行%d ",y,h_sum[y]); } int h_mid=(h_sum[height/2]+h_sum[height/2-10]+h_sum[height/2-20]+h_sum[height/2-30]+h_sum[height/2-40]); h_mid=h_mid/5; for ( int y=height/2;y<height;y++) { if ((h_sum[y]-h_mid)>255*35) { below=y; break ; } } for ( int y=height/2;y>0;y--) { if ((h_sum[y]-h_mid)>255*37) { up=y; break ; } } for ( int x=0;x<width;x++) { w_sum[x]=0; for ( int y=up;y<below;y++) { w_sum[x]=w_sum[x]+src[y*width+x]; } //printf("%d列%d ",x,w_sum[x]); } int right_start=width-10; for ( int x=width-10;x>width/2;x--) { if (w_sum[x]!=(below-up)*255) { right_start=x; break ; } } for ( int x=right_start-45;x>width/2;x--) { if (w_sum[x]<255*(below-up-40)) { right=x; break ; } } int left_start=10; for ( int x=10;x<width;x++) { if (w_sum[x]!=(below-up)*255) { left_start=x; break ; } } for ( int x=left_start+100;x<width;x++) { if (w_sum[x]<255*(below-up-20)) { left=x; break ; } } for ( int y=up;y<below;y++) { for ( int x=left-5;x<right+5;x++) { des[y*width+x]=src[y*width+x]; } } printf ( "up%d below%d left%d right%d left_start%d h_mid%d height/2%d width%d" ,up, below,left, right,left_start,h_mid,height/2,width); } 1.3结果展示 |
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步