¿Cómo hacer que el texto de la pizarra aparezca más claro con MATLAB?

¿Cuál es la secuencia de filtros que debo poner si quiero que la imagen final sea más clara con un aspecto de tipo digital? Me refiero solo a dos colores distintos, uno para el tablero y otro para la escritura con tiza.
Imagen de pizarra

Cuando se trata de identificar texto en imágenes, es mejor utilizar la Transformación del ancho de trazo .

Aquí hay un pequeño resultado que obtuve en su imagen (la transformación básica + componente conectado sin filtro): Imagen transformada SWT

Mi implementación mex basada en código de aquí


 #include "mex.h" #include  #include  #include  #include  #include  using namespace std; #define PI 3.14159265 struct Point2d { int x; int y; float SWT; }; struct Point2dFloat { float x; float y; }; struct Ray { Point2d p; Point2d q; std::vector points; }; void strokeWidthTransform(const float * edgeImage, const float * gradientX, const float * gradientY, bool dark_on_light, float * SWTImage, int h, int w, std::vector & rays) { // First pass float prec = .05f; for( int row = 0; row < h; row++ ){ const float* ptr = edgeImage + row*w; for ( int col = 0; col < w; col++ ){ if (*ptr > 0) { Ray r; Point2d p; px = col; py = row; rp = p; std::vector points; points.push_back(p); float curX = (float)col + 0.5f; float curY = (float)row + 0.5f; int curPixX = col; int curPixY = row; float G_x = gradientX[ col + row*w ]; float G_y = gradientY[ col + row*w ]; // normalize gradient float mag = sqrt( (G_x * G_x) + (G_y * G_y) ); if (dark_on_light){ G_x = -G_x/mag; G_y = -G_y/mag; } else { G_x = G_x/mag; G_y = G_y/mag; } while (true) { curX += G_x*prec; curY += G_y*prec; if ((int)(floor(curX)) != curPixX || (int)(floor(curY)) != curPixY) { curPixX = (int)(floor(curX)); curPixY = (int)(floor(curY)); // check if pixel is outside boundary of image if (curPixX < 0 || (curPixX >= w) || curPixY < 0 || (curPixY >= h)) { break; } Point2d pnew; pnew.x = curPixX; pnew.y = curPixY; points.push_back(pnew); if ( edgeImage[ curPixY*w+ curPixX ] > 0) { rq = pnew; // dot product float G_xt = gradientX[ curPixY*w + curPixX ]; float G_yt = gradientY[ curPixY*w + curPixX ]; mag = sqrt( (G_xt * G_xt) + (G_yt * G_yt) ); if (dark_on_light){ G_xt = -G_xt/mag; G_yt = -G_yt/mag; } else { G_xt = G_xt/mag; G_yt = G_yt/mag; } if (acos(G_x * -G_xt + G_y * -G_yt) < PI/2.0 ) { float length = sqrt( ((float)rqx - (float)rpx)*((float)rqx - (float)rpx) + ((float)rqy - (float)rpy)*((float)rqy - (float)rpy)); for (std::vector::iterator pit = points.begin(); pit != points.end(); pit++) { float* pSWT = SWTImage + w * pit->y + pit->x; if (*pSWT < 0) { *pSWT = length; } else { *pSWT = std::min(length, *pSWT); } } r.points = points; rays.push_back(r); } break; } } } } ptr++; } } } bool Point2dSort(const Point2d &lhs, const Point2d &rhs) { return lhs.SWT < rhs.SWT; } void SWTMedianFilter(float * SWTImage, int h, int w, std::vector & rays, float maxWidth = -1 ) { for (std::vector::iterator rit = rays.begin(); rit != rays.end(); rit++) { for (std::vector::iterator pit = rit->points.begin(); pit != rit->points.end(); pit++) { pit->SWT = SWTImage[ w*pit->y + pit->x ]; } std::sort(rit->points.begin(), rit->points.end(), &Point2dSort); //std::nth_element( rit->points.begin(), rit->points.end(), rit->points.size()/2, &Point2dSort ); float median = (rit->points[rit->points.size()/2]).SWT; if ( maxWidth > 0 && median >= maxWidth ) { median = -1; } for (std::vector::iterator pit = rit->points.begin(); pit != rit->points.end(); pit++) { SWTImage[ w*pit->y + pit->x ] = std::min(pit->SWT, median); } } } typedef std::vector< std::set > graph_t; // graph as a list of neighbors per node void connComp( const graph_t& g, std::vector& c, int i, int l ) { // starting from node i labe this conn-comp with label l if ( i < 0 || i > g.size() ) { return; } std::vector< int > stack; // push i stack.push_back(i); c[i] = l; while ( ! stack.empty() ) { // pop i = stack.back(); stack.pop_back(); // go over all nieghbors for ( std::set::const_iterator it = g[i].begin(); it != g[i].end(); it++ ) { if ( c[*it] < 0 ) { stack.push_back( *it ); c[ *it ] = l; } } } } int findNextToLabel( const graph_t& g, const vector& c ) { for ( int i = 0 ; i < c.size(); i++ ) { if ( c[i] < 0 ) { return i; } } return c.size(); } int connected_components(const graph_t& g, vector& c) { // check for empty graph! if ( g.empty() ) { return 0; } int i = 0; int num_conn = 0; do { connComp( g, c, i, num_conn ); num_conn++; i = findNextToLabel( g, c ); } while ( i < g.size() ); return num_conn; } std::vector< std::vector > findLegallyConnectedComponents(const float* SWTImage, int h, int w, std::vector & rays) { std::map Map; std::map revmap; std::vector > components; // empty int num_vertices = 0, idx = 0; graph_t g; // Number vertices for graph. Associate each point with number for( int row = 0; row < h; row++ ){ for (int col = 0; col < w; col++ ){ idx = col + w * row; if (SWTImage[idx] > 0) { Map[idx] = num_vertices; Point2d p; px = col; py = row; revmap[num_vertices] = p; num_vertices++; std::set empty; g.push_back(empty); } } } if ( g.empty() ) { return components; // nothing to do with an empty graph... } for( int row = 0; row < h; row++ ){ for (int col = 0; col < w; col++ ){ idx = col + w * row; if ( SWTImage[idx] > 0) { // check pixel to the right, right-down, down, left-down int this_pixel = Map[idx]; float thisVal = SWTImage[idx]; if (col+1 < w) { float right = SWTImage[ w*row + col + 1 ]; if (right > 0 && (thisVal/right < = 3.0 || right/thisVal <= 3.0)) { g[this_pixel].insert( Map[ w*row + col + 1 ] ); g[ Map[ w*row + col + 1 ] ].insert( this_pixel ); //boost::add_edge(this_pixel, map.at(row * SWTImage->width + col + 1), g); } } if (row+1 < h) { if (col+1 < w) { float right_down = SWTImage[ w*(row+1) + col + 1 ]; if (right_down > 0 && (thisVal/right_down < = 3.0 || right_down/thisVal <= 3.0)) { g[ this_pixel ].insert( Map[ w*(row+1) + col + 1 ] ); g[ Map[ w*(row+1) + col + 1 ] ].insert(this_pixel); // boost::add_edge(this_pixel, map.at((row+1) * SWTImage->width + col + 1), g); } } float down = SWTImage[ w*(row+1) + col ]; if (down > 0 && (thisVal/down < = 3.0 || down/thisVal <= 3.0)) { g[ this_pixel ].insert( Map[ w*(row+1) + col ] ); g[ Map[ w*(row+1) + col ] ].insert( this_pixel ); //boost::add_edge(this_pixel, map.at((row+1) * SWTImage->width + col), g); } if (col-1 >= 0) { float left_down = SWTImage[ w*(row+1) + col - 1 ]; if (left_down > 0 && (thisVal/left_down < = 3.0 || left_down/thisVal <= 3.0)) { g[ this_pixel ].insert( Map[ w*(row+1) + col - 1 ] ); g[ Map[ w*(row+1) + col - 1 ] ].insert( this_pixel ); //boost::add_edge(this_pixel, map.at((row+1) * SWTImage->width + col - 1), g); } } } } } } std::vector c(num_vertices, -1); int num_comp = connected_components(g, c); components.reserve(num_comp); //std::cout < < "Before filtering, " << num_comp << " components and " << num_vertices << " vertices" << std::endl; for (int j = 0; j < num_comp; j++) { std::vector tmp; components.push_back( tmp ); } for (int j = 0; j < num_vertices; j++) { Point2d p = revmap[j]; (components[c[j]]).push_back(p); } return components; } enum { EIN = 0, GXIN, GYIN, DOLFIN, MAXWIN, NIN }; void mexFunction( int nout, mxArray* pout[], int nin, const mxArray* pin[] ) { // // make sure images are input in transposed so that they are arranged row-major in memory // mxAssert( nin == NIN, "wrong number of inputs" ); mxAssert( nout > 1, "only one output" ); int h = mxGetN( pin[EIN] ); // inputs are transposed! int w = mxGetM( pin[EIN] ); mxAssert( mxIsClass( pin[EIN], mxSINGLE_CLASS ) && h == mxGetN( pin[EIN] ) && w == mxGetM( pin[EIN] ), "edge map incorrect"); mxAssert( mxIsClass( pin[GXIN], mxSINGLE_CLASS ) && h == mxGetN( pin[GXIN] ) && w == mxGetM( pin[GXIN] ), "edge map incorrect"); mxAssert( mxIsClass( pin[GYIN], mxSINGLE_CLASS ) && h == mxGetN( pin[GYIN] ) && w == mxGetM( pin[GYIN] ), "edge map incorrect"); const float * edgeImage = (float*) mxGetData( pin[EIN] ); const float * gradientX = (float*) mxGetData( pin[GXIN] ); const float * gradientY = (float*) mxGetData( pin[GYIN] ); bool dark_on_light = mxGetScalar( pin[DOLFIN] ) != 0 ; float maxWidth = mxGetScalar( pin[MAXWIN] ); // allocate output pout[0] = mxCreateNumericMatrix( w, h, mxSINGLE_CLASS, mxREAL ); float * SWTImage = (float*) mxGetData( pout[0] ); // set SWT to -1 for ( int i = 0 ; i < w*h; i++ ) { SWTImage[i] = -1; } std::vector rays; strokeWidthTransform ( edgeImage, gradientX, gradientY, dark_on_light, SWTImage, h, w, rays ); SWTMedianFilter ( SWTImage, h, w, rays, maxWidth ); // connected components if ( nout > 1 ) { // Calculate legally connect components from SWT and gradient image. // return type is a vector of vectors, where each outer vector is a component and // the inner vector contains the (y,x) of each pixel in that component. std::vector > components = findLegallyConnectedComponents(SWTImage, h, w, rays); pout[1] = mxCreateNumericMatrix( w, h, mxSINGLE_CLASS, mxREAL ); float* pComp = (float*) mxGetData( pout[1] ); for ( int i = 0 ; i < w*h; i++ ) { pComp[i] = 0; } for ( int ci = 0 ; ci < components.size(); ci++ ) { for ( std::vector::iterator it = components[ci].begin() ; it != components[ci].end(); it++ ) { pComp[ w * it->y + it->x ] = ci + 1; } } } } 

Función de Matlab que llama a archivo-mex de transformación de ancho de desplazamiento (SWT):

 function [swt swtcc] = SWT( img, dol, maxWidth ) if size( img, 3 ) == 3 img = rgb2gray(img); end img = im2single(img); edgeMap = single( edge( img, 'canny', .15 ) ); img = imfilter( img, fspecial('gauss',[5 5], 0.3*(2.5-1)+.8) ); gx = imfilter( img, fspecial('prewitt')' ); %//' gy = imfilter( img, fspecial('prewitt') ); gx = single(medfilt2( gx, [3 3] )); gy = single(medfilt2( gy, [3 3] )); [swt swtcc] = swt_mex( edgeMap.', gx.', gy.', dol, maxWidth ); %//' swt = swt'; %//' swtcc = double(swtcc'); %//' 

Prueba esto :

 I = imread('...'); % Your board image ThreshConstant = 1; % Try to vary this constant. bw = im2bw(I , ThreshConstant * graythresh(I)); % Black-white image SegmentedImg = I.*repmat(uint8(bw), [1 1 3]); 

Solo imshow(bw); y tendrá una imagen de 2 colores normalmente bien segmentada.

Si el umbral es demasiado fuerte, intente girar alrededor de 0.5 a 1.5 con ThreshConstant .

o podrías intentar esto

 im = imread('http://i.imgur.com/uJIXp13.jpg'); %the image posted above im2=rgb2gray(im); maxp=uint16(max(max(im2))); minp=uint16(min(min(im2))); bw=im2bw(im2,(double(minp+maxp))/(2*255)); %the threshold as alexandre said, but with the min max idensity as threshold bw=~bw; % you need to reverse from black font - whit letters to black letters white font :P imshow(bw) 

este debería ser el resultado

enter image description here tenga en cuenta que puede usar esta técnica de forma adaptativa con una ventana, encontrando el umbral de la ventana cada vez para obtener mejores resultados.