车牌字符分割
我遇到了一个问题,想从车牌图片中分割出字符。为了解决这个问题,我采取了以下方法来提取车牌上的字符:
- 对车牌图片进行自适应阈值处理。
- 选择具有特定长宽比的轮廓。
但是,如果车牌图片中有阴影(就像附带的文件那样),我就无法正确分割字符,因为二值化处理不够好。阴影会把相邻的字符混在一起。
我尝试了不同窗口大小的阈值处理,结果如附图所示。那么,如果图片中有阴影,我该如何从中分割出字符呢?我正在使用OpenCV。
我在OpenCV中使用了以下函数来处理我的车牌图片:
cvAdaptiveThreshold(licensePlateImg, threshImg, 255, CV_ADAPTIVE_THRESH_GAUSSIAN_C, CV_THRESH_BINARY_INV, wind);
我尝试了不同的窗口大小(wind
)和不同的adaptiveMethod
(ADAPTIVE_THRESH_MEAN_C 和 ADAPTIVE_THRESH_GAUSSIAN_C
),以获得阈值处理后的图片。
2 个回答
我觉得如果你对你提供的第二张二值化图像进行形态学开运算,效果会很好。
在我开始之前,我知道你想要在OpenCV C++中实现这个算法,但我的算法需要使用FFT,而numpy / scipy
这两个包在这方面非常好用。因此,我将给你一个在OpenCV中用Python实现的算法。其实,这段代码和C++的API非常相似,你可以很容易地把它转写过去。这样可以节省我学习(或者说重新学习)API的时间,我更希望把算法和我执行这个任务的步骤告诉你,以免浪费时间。
接下来,我会给你一个我会做的总体概述。然后我会展示使用numpy, scipy
和OpenCV包的Python代码。对于使用MATLAB的人,我还会展示MATLAB的等效代码,顺便给你提供MATLAB代码!
你可以尝试使用同态滤波。简单来说,我们可以把一幅图像看作是光照和反射的乘积。光照被认为是变化缓慢的,主要影响动态范围。这实际上是低频内容。反射则代表物体的细节,变化较快。这也是局部对比度的主要贡献者,属于高频内容。
图像可以表示为这两者的乘积。同态滤波试图将这些成分分开,并对它们进行单独滤波。完成后,我们再把结果合并在一起。由于这是一个乘法模型,通常会使用对数运算,这样我们就可以把乘积表示为两个项的和。这两个项分别进行滤波,调整它们对图像的贡献,然后相加,最后再取反对数。
阴影是由于光照造成的,因此我们可以减少这种阴影对图像的影响。同时,我们可以增强反射,以便获得更好的边缘,因为边缘与高频信息相关。
我们通常使用低通滤波器来滤波光照,而使用高通滤波器来滤波反射。在这种情况下,我选择一个标准差为10的高斯核作为低通滤波器。高通滤波器可以通过用1
减去低通滤波器来获得。我将图像转换到对数域,然后在频域中使用低通和高通滤波器对图像进行滤波。接着,我调整低通和高通的结果,将这些成分加回来,然后取反对数。现在,这幅图像更适合进行阈值处理,因为图像的变化较小。
作为额外的后处理,我会对图像进行阈值处理。字母的颜色比整体背景要深,因此任何低于某个阈值的像素都会被归类为文本。我选择的阈值是强度65。之后,我还会清除任何接触边界的像素,然后移除图像中总面积小于160(MATLAB)或120(Python)像素的区域。我还会裁剪掉一些不需要的列,因为它们对我们的分析没有帮助。
这里有几个注意事项:
注意事项 #1 - 移除边界
移除任何接触边界的像素在OpenCV中并没有内置。不过,MATLAB有一个类似的功能叫做imclearborder
。我会在我的MATLAB代码中使用这个,但在OpenCV中,我采用了以下算法:
- 找到图像中的所有轮廓
- 对于图像中的每个轮廓,检查是否有任何轮廓像素在图像的边界内
- 如果有,标记这个轮廓以便移除
- 对于每个要移除的轮廓,简单地将整个轮廓绘制成黑色
我在代码中创建了一个叫imclearborder(imgBW, radius)
的方法,其中radius
是你想要清除的边界内的像素数量。
注意事项 #2 - 移除小于某个面积的像素区域
移除小于某个面积的区域在OpenCV中同样并没有实现。在MATLAB中,这个功能可以通过bwareaopen
方便地实现。基本算法如下:
- 找到图像中的所有轮廓
- 分析每个轮廓的面积,如果填充内部会占据多少空间
- 对于任何小于某个面积的区域,通过填充内部为黑色来清除这个轮廓
我创建了一个叫bwareaopen(imgBW)
的方法来为我们完成这个操作。
注意事项 #3 - 移除像素区域的面积参数
对于Python代码,我需要调整这个参数,最后选择了120。MATLAB使用的是160。对于Python,120去掉了一些字符,这是不希望的。我猜我的bwareaopen
实现和MATLAB的不同,这可能是我得到不同结果的原因。
不再赘述,这里是代码。请注意,我没有使用空间滤波。你可以在OpenCV中使用filter2D
对图像与高斯核进行卷积,但我没有这样做,因为同态滤波在使用低通和高通滤波器时通常是在频域中进行的。你可以尝试使用空间滤波,但你还需要提前知道核的大小。而频域滤波只需要知道滤波器的标准差,这只是一个参数,相比之下要简单得多。
另外,对于Python代码,我把你的图像下载到我的电脑上并运行了脚本。对于MATLAB,你可以直接引用图像的超链接来读取它。
Python代码
import cv2 # For OpenCV modules (For Image I/O and Contour Finding)
import numpy as np # For general purpose array manipulation
import scipy.fftpack # For FFT2
#### imclearborder definition
def imclearborder(imgBW, radius):
# Given a black and white image, first find all of its contours
imgBWcopy = imgBW.copy()
contours,hierarchy = cv2.findContours(imgBWcopy.copy(), cv2.RETR_LIST,
cv2.CHAIN_APPROX_SIMPLE)
# Get dimensions of image
imgRows = imgBW.shape[0]
imgCols = imgBW.shape[1]
contourList = [] # ID list of contours that touch the border
# For each contour...
for idx in np.arange(len(contours)):
# Get the i'th contour
cnt = contours[idx]
# Look at each point in the contour
for pt in cnt:
rowCnt = pt[0][1]
colCnt = pt[0][0]
# If this is within the radius of the border
# this contour goes bye bye!
check1 = (rowCnt >= 0 and rowCnt < radius) or (rowCnt >= imgRows-1-radius and rowCnt < imgRows)
check2 = (colCnt >= 0 and colCnt < radius) or (colCnt >= imgCols-1-radius and colCnt < imgCols)
if check1 or check2:
contourList.append(idx)
break
for idx in contourList:
cv2.drawContours(imgBWcopy, contours, idx, (0,0,0), -1)
return imgBWcopy
#### bwareaopen definition
def bwareaopen(imgBW, areaPixels):
# Given a black and white image, first find all of its contours
imgBWcopy = imgBW.copy()
contours,hierarchy = cv2.findContours(imgBWcopy.copy(), cv2.RETR_LIST,
cv2.CHAIN_APPROX_SIMPLE)
# For each contour, determine its total occupying area
for idx in np.arange(len(contours)):
area = cv2.contourArea(contours[idx])
if (area >= 0 and area <= areaPixels):
cv2.drawContours(imgBWcopy, contours, idx, (0,0,0), -1)
return imgBWcopy
#### Main program
# Read in image
img = cv2.imread('5DnwY.jpg', 0)
# Number of rows and columns
rows = img.shape[0]
cols = img.shape[1]
# Remove some columns from the beginning and end
img = img[:, 59:cols-20]
# Number of rows and columns
rows = img.shape[0]
cols = img.shape[1]
# Convert image to 0 to 1, then do log(1 + I)
imgLog = np.log1p(np.array(img, dtype="float") / 255)
# Create Gaussian mask of sigma = 10
M = 2*rows + 1
N = 2*cols + 1
sigma = 10
(X,Y) = np.meshgrid(np.linspace(0,N-1,N), np.linspace(0,M-1,M))
centerX = np.ceil(N/2)
centerY = np.ceil(M/2)
gaussianNumerator = (X - centerX)**2 + (Y - centerY)**2
# Low pass and high pass filters
Hlow = np.exp(-gaussianNumerator / (2*sigma*sigma))
Hhigh = 1 - Hlow
# Move origin of filters so that it's at the top left corner to
# match with the input image
HlowShift = scipy.fftpack.ifftshift(Hlow.copy())
HhighShift = scipy.fftpack.ifftshift(Hhigh.copy())
# Filter the image and crop
If = scipy.fftpack.fft2(imgLog.copy(), (M,N))
Ioutlow = scipy.real(scipy.fftpack.ifft2(If.copy() * HlowShift, (M,N)))
Iouthigh = scipy.real(scipy.fftpack.ifft2(If.copy() * HhighShift, (M,N)))
# Set scaling factors and add
gamma1 = 0.3
gamma2 = 1.5
Iout = gamma1*Ioutlow[0:rows,0:cols] + gamma2*Iouthigh[0:rows,0:cols]
# Anti-log then rescale to [0,1]
Ihmf = np.expm1(Iout)
Ihmf = (Ihmf - np.min(Ihmf)) / (np.max(Ihmf) - np.min(Ihmf))
Ihmf2 = np.array(255*Ihmf, dtype="uint8")
# Threshold the image - Anything below intensity 65 gets set to white
Ithresh = Ihmf2 < 65
Ithresh = 255*Ithresh.astype("uint8")
# Clear off the border. Choose a border radius of 5 pixels
Iclear = imclearborder(Ithresh, 5)
# Eliminate regions that have areas below 120 pixels
Iopen = bwareaopen(Iclear, 120)
# Show all images
cv2.imshow('Original Image', img)
cv2.imshow('Homomorphic Filtered Result', Ihmf2)
cv2.imshow('Thresholded Result', Ithresh)
cv2.imshow('Opened Result', Iopen)
cv2.waitKey(0)
cv2.destroyAllWindows()
MATLAB代码
clear all;
close all;
% Read in image
I = imread('https://i.stack.imgur.com/5DnwY.jpg');
% Remove some columns from the beginning and end
I = I(:,60:end-20);
% Cast to double and do log. We add with 1 to avoid log(0) error.
I = im2double(I);
I = log(1 + I);
% Create Gaussian mask in frequency domain
% We must specify our mask to be twice the size of the image to avoid
% aliasing.
M = 2*size(I,1) + 1;
N = 2*size(I,2) + 1;
sigma = 10;
[X, Y] = meshgrid(1:N,1:M);
centerX = ceil(N/2);
centerY = ceil(M/2);
gaussianNumerator = (X - centerX).^2 + (Y - centerY).^2;
% Low pass and high pass filters
Hlow = exp(-gaussianNumerator./(2*sigma.^2));
Hhigh = 1 - Hlow;
% Move origin of filters so that it's at the top left corner to match with
% input image
Hlow = ifftshift(Hlow);
Hhigh = ifftshift(Hhigh);
% Filter the image, and crop
If = fft2(I, M, N);
Ioutlow = real(ifft2(Hlow .* If));
Iouthigh = real(ifft2(Hhigh .* If));
% Set scaling factors then add
gamma1 = 0.3;
gamma2 = 1.5;
Iout = gamma1*Ioutlow(1:size(I,1),1:size(I,2)) + ...
gamma2*Iouthigh(1:size(I,1),1:size(I,2));
% Anti-log then rescale to [0,1]
Ihmf = exp(Iout) - 1;
Ihmf = (Ihmf - min(Ihmf(:))) / (max(Ihmf(:)) - min(Ihmf(:)));
% Threshold the image - Anything below intensity 65 gets set to white
Ithresh = Ihmf < 65/255;
% Remove border pixels
Iclear = imclearborder(Ithresh, 8);
% Eliminate regions that have areas below 160 pixels
Iopen = bwareaopen(Iclear, 160);
% Show all of the results
figure;
subplot(4,1,1);
imshow(I);
title('Original Image');
subplot(4,1,2);
imshow(Ihmf);
title('Homomorphic Filtered Result');
subplot(4,1,3);
imshow(Ithresh);
title('Thresholded Result');
subplot(4,1,4);
imshow(Iopen);
title('Opened Result');
这是我得到的结果:
Python
请注意,我重新排列了窗口,使它们对齐在一列中。