最近在看EasyPR的实现教程,感觉还不错,自己也通过编程实现了几个小模块。写下自己的心得,一来是总结下最近的学习内容,二来是以后看的时候聊以慰藉。一直走,难免会错过路边的花朵,现在回过头来看看,采采野花,也对自己是个交代。
EasyPR是一个中文车牌识别系统,Git地址为:https:://github.com/liuruoze/EasyPR。
因为最近在学习VTK的使用,所以想利用VTK来实现类似的功能,巩固学习的内容。但是我发现VTK在很多功能上没有OpenCV强大,毕竟VTK主要针对于医学图像,对体绘制方面有很好的处理效果,而OpenCV对于普通的图片处理的功能上比VTK多很多,包括继承了机器学习的模块。(VTK可能和机器学习的模块结合?可以试试)
方法是一致的,可能具体实现不同。
针对于车牌识别,无外乎两类图片,包含车牌的图片和不包含车牌的图片。其实处理起来是一样的,因为不包含车牌的图片可能不具备车牌的特征,最后输出结果为空,其实都可以一样处理。
下面是一个车牌的图片。
这个车牌比较清晰,也是大多数情况下能看到的,车牌方方正正,颜色为蓝色,与其他背景色不相同,所以我们第一感觉就是颜色定位,定位到蓝色的那一块,然后提取特定区域就能将车牌提取出来了。
对于彩色图像,最常用的是RGB编码格式,R代表红,G代表绿,B代表蓝,对应自然界中的三原色。
那么什么样的比例是蓝色呢?
对应的RGB值很乱,不能按照一定的规则判断,或者说判断起来很麻烦。那么有没有其他的颜色模式,让蓝色出于一个规则的范围里面呢?除了RGB,还有YUV和HSV。具体参考https://www.cnblogs.com/justkong/p/6570914.html
可以发现,HSV颜色空间模型符合我们的预期。H表示色相(Hue),S表示饱和度(Saturation),V表示明度(value)。具体可以参考https://html-color-codes.info/old/colorpicker.html
通过颜色表可以发现,当H在200-260的范围内,属于蓝色。当H属于200-220时,浅蓝,S,V(80%-100%)大些,属于蓝色。当H属于220-240时,中蓝,S,V(60%-100%)即可属于蓝色。当H属于240-260时,深蓝,S,V(70%-100%)属于蓝色。
1、将图片由RGB转化为HSV
转换公式如下:
//传入读入的图片数据
vtkImageData *colorLocate(vtkImageData *data)
{
//待处理图片
vtkImageData* filter =
vtkImageData::New();
filter->DeepCopy(data);
//转换为HSV
int dims[3];
filter->GetDimensions(dims);
for (int k = 0; k < dims[2]; k++)
{
for (int j = 0; j < dims[1]; j++)
{
for (int i = 0; i < dims[0]; i++)
{
unsigned char *pixel = (unsigned char*)(filter->GetScalarPointer(i, j, k));
RGB2HSV(pixel);
}
}
}
filter->Modified();
cout << "转换成功" << endl;
return filter;
}
void RGB2HSV(unsigned char *p)
{
double onethird = 1.0 / 3.0;
double onesixth = 1.0 / 6.0;
double twothird = 2.0 / 3.0;
double R, G, B, H, S, V;
double x = 255;
R = static_cast(*p) / x;//R
G = static_cast(*(p + 1)) / x; //G
B = static_cast(*(p + 2)) / x; //B
double Cmax = max(R, G, B);
double Cmin = min(R, G, B);
V = Cmax;
if (V == 0)
S = 0.0;
else
{
S = (Cmax - Cmin) / Cmax;
}
if (S > 0)
{
if (R == Cmax)
H = onesixth * (G - B) / (Cmax - Cmin);
else if (G == Cmax)
H = onethird + onesixth * (B - R) / (Cmax - Cmin);
else if (B == Cmax)
H = twothird + onesixth * (R - G) / (Cmax - Cmin);
}
else
H = 0.0;
H = H * x;
S = S * x;
V = V * x;
if (H > x)
{
H = x;
}
if (S > x)
{
S = x;
}
if (V > x)
{
V = x;
}
*p = H;
*(p + 1) = S;
*(p + 2) = V;
//cout << "H:" << H << "S:" << S << "V:" << V << endl;
}
因为图片显示的是VtkImageActor类,vtkImageActor接收的图像vtkImageData数据类型必须为unsigned char类型,所以将HSV的值映射到0-255的方位内。
具体转换显示如下:
由于他按照RGB的形式,所以显示不一样,不过没关系,处理还是一样的。
2、确定蓝色区域
判断条件如下:H属于0-360,映射到0-255范围,S,V规则一样。
vector region; //保存区域坐标
int dims[3];
imgData->GetDimensions(dims);
for (int k = 0; k < dims[2]; k++)
{
for (int j = 0; j < dims[1]; j++)
{
for (int i = 0; i < dims[0]; i++)
{
unsigned char *p = static_cast(imgData->GetScalarPointer(i, j, k));
//H:200-220;S,V:80%-100%
if (*p >= 141.666 && *p <= 198.333 && *(p + 1) >= 89.25&&*(p + 1) <= 255 && *(p + 2) >= 89.25&&*(p + 2) <= 255)
{
region.push_back(i);
region.push_back(j);
region.push_back(k);
}
}
}
}
3、将原图二值化,该区域标记为白色,其他区域标记为黑色
vtkImageLuminance* luminanceFilter =
vtkImageLuminance::New();
luminanceFilter->SetInputConnection(reader->GetOutputPort());
luminanceFilter->Update();
vtkImageThreshold* thresholdFilter =
vtkImageThreshold::New();
thresholdFilter->SetInputConnection(luminanceFilter->GetOutputPort());
thresholdFilter->ThresholdByUpper(255);
thresholdFilter->SetInValue(255);
thresholdFilter->SetOutValue(0);
thresholdFilter->Update();
thresholdFilter->GetOutput()->GetDimensions(dims);
int numOfComp = thresholdFilter->GetOutput()->GetNumberOfScalarComponents();
cout << numOfComp << endl; //单通道
vector::iterator itr;
for (itr = region.begin(); itr != region.end(); itr = itr + 3)
{
unsigned char *pixel = (unsigned char*)(thresholdFilter->GetOutput()->GetScalarPointer(*itr, *(itr + 1), *(itr + 2)));
*pixel = 255;
}
4、闭操作
通过闭操作,就能将车牌区域提取出来,变成一块,为后面操作提供方便。
VTK中没有实现闭操作的类,我打算通过C++代码实现。
这里碰到瓶颈了。
还是硬着头皮继续搞吧。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
闭操作后的效果
5、求最小外接矩形,并绘制在原图上。