如何使用 MATLAB 让黑板文字显得更清晰?
Posted
技术标签:
【中文标题】如何使用 MATLAB 让黑板文字显得更清晰?【英文标题】:How to make the blackboard text appear clearer using MATLAB? 【发布时间】:2013-11-26 11:17:00 【问题描述】:如果我希望最终图像更清晰,具有数字类型的外观,我应该放置什么样的滤镜序列。我的意思是只有两种不同的颜色,一种用于白板,一种用于粉笔书写。
【问题讨论】:
你尝试过什么吗?如果可以,可以分享一下代码吗? 我首先转换为灰度图像并应用中值滤波器(matlab 中窗口大小为 7x7 的 medfilt2)。虽然我不想将最终图像作为灰度图像。 @Shivendra 仔细查看董事会:(a-b)(a+b)=a^2-b^2
和 不是 a^2+b^2
!
@Shai 你把我带到了那里。哎呀,我的坏。我乱写得很仓促。虽然那是不可原谅的。 :)
【参考方案1】:
在识别图像中的文本时,您最好使用Stroke Width Transform。
这是我在您的图像上获得的一个小结果(基本变换 + 不带过滤的连接组件):
我的 mex 实现基于来自 here 的代码
#include "mex.h"
#include <vector>
#include <map>
#include <set>
#include <algorithm>
#include <math.h>
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<Point2d> points;
;
void strokeWidthTransform(const float * edgeImage,
const float * gradientX,
const float * gradientY,
bool dark_on_light,
float * SWTImage,
int h, int w,
std::vector<Ray> & 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;
p.x = col;
p.y = row;
r.p = p;
std::vector<Point2d> 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)
r.q = 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)r.q.x - (float)r.p.x)*((float)r.q.x - (float)r.p.x) + ((float)r.q.y - (float)r.p.y)*((float)r.q.y - (float)r.p.y));
for (std::vector<Point2d>::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<Ray> & rays, float maxWidth = -1 )
for (std::vector<Ray>::iterator rit = rays.begin(); rit != rays.end(); rit++)
for (std::vector<Point2d>::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<Point2d>::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<int> > graph_t; // graph as a list of neighbors per node
void connComp( const graph_t& g, std::vector<int>& 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<int>::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<int>& 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<int>& 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<Point2d> >
findLegallyConnectedComponents(const float* SWTImage, int h, int w,
std::vector<Ray> & rays)
std::map<int, int> Map;
std::map<int, Point2d> revmap;
std::vector<std::vector<Point2d> > 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;
p.x = col;
p.y = row;
revmap[num_vertices] = p;
num_vertices++;
std::set<int> 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<int> 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<Point2d> 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<Ray> 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<std::vector<Point2d> > 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<Point2d>::iterator it = components[ci].begin() ; it != components[ci].end(); it++ )
pComp[ w * it->y + it->x ] = ci + 1;
Matlab 函数调用 stroke-width-transform (SWT) mex-file:
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'); %//'
【讨论】:
感谢出色的实现。为什么会有这么多颜色出现?我不能为文本设置单一颜色吗? @Shivendra 颜色代表不同的笔画。阅读论文,您会在其中找到更多详细信息。 非常好的代码@Shai。你能澄清一些事情吗?我想我明白 maxwidth 的用途,但是 dol 呢?另外,你如何显示颜色?我只设法从此代码(swtcc)中获得黑白图像,而 swt 似乎有宽度信息。没有笔画宽度的部分为-1,其他部分为笔画宽度。提前致谢 @DiamantatosParaskevasdol
是“暗光”文本的二进制标志。该算法将根据此标志跟踪笔画。您可以使用 ind2rgb
将组件的索引从 swtcc
转换为颜色
@Shai 希望有一个 Python/OpenCV 实现!【参考方案2】:
试试这个:
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]);
只需执行imshow(bw);
,您将获得正常分割良好的 2 色图像。
如果阈值太强,请尝试使用ThreshConstant
将 0.5 转为 1.5。
【讨论】:
这几乎可以工作了。但是,当我增加 ThreshConstant 时,写作并不顺利,我必须这样做以删除不需要的像素。写作中也有断点。感谢您让我开始。 您要求获得两种不同的颜色,im2bw
是实现它的最简单方法。也许,您需要对图像进行预处理以平滑事物,并可能在图像上获得均匀的照明。如果你看左上角,图像比右下角更闪亮。这可以稍微改变阈值。【参考方案3】:
或者你可以试试这个
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)
这应该是结果
请记住,您可以对窗口自适应地使用此技术,每次都找到窗口的阈值以获得最佳结果。
【讨论】:
以上是关于如何使用 MATLAB 让黑板文字显得更清晰?的主要内容,如果未能解决你的问题,请参考以下文章
matlab中如何将输出的figure中的图像保存在我的文档中