Реализация алгоритма обнаружения номерного знака
Чтобы улучшить свои знания в области визуализации и получить некоторый опыт работы с темами, я решил создать алгоритм распознавания номерных знаков на платформе Android.
Первым шагом является обнаружение, для которого я решил реализовать недавний документ под названием "Надежный и эффективный подход к обнаружению номерных знаков". В статье очень хорошо представлена их идея и используются довольно простые методы для обнаружения. Помимо некоторых деталей, отсутствующих в статье, я реализовал билинейную понижающую дискретизацию, преобразование в оттенки серого и адаптивный порог с окантовкой +, как описано в Разделах 3A, 3B.1 и 3B.2. К сожалению, я не получаю вывод, представленный в этом документе, например, на рисунках 3 и 6.
Изображение, которое я использую для тестирования, выглядит следующим образом:
Версия с серой шкалой (и с пониженной дискретизацией) выглядит отлично (фактическая реализация приведена в нижней части этого поста), я использовал хорошо известную комбинацию компонентов RGB для ее создания (в статье не упоминается, как, поэтому я предположил),
Далее идет начальное обнаружение края с использованием выделенного фильтра Собеля. Это создает изображение, аналогичное представленному на рисунке 6 документа.
И, наконец, удалите "слабые края", к которым они применяют адаптивный порог, используя окно 20x20. Здесь все идет не так.
Как вы можете видеть, он не работает должным образом, хотя я использую их заявленные значения параметров. Дополнительно я попробовал:
- Изменение бета-параметра.
- Используйте массив 2d int вместо растровых объектов, чтобы упростить создание целостного изображения.
- Попробуйте более высокий параметр Gamma, чтобы при начальном обнаружении фронта было больше "краев".
- Измените окно, например, 10x10.
Все же ни одно из изменений не сделало улучшение; он продолжает создавать изображения, как показано выше. Мой вопрос: что я делаю иначе, чем указано в статье? и как я могу получить желаемый результат?
Код
(Очищенный) код, который я использую:
public int[][] toGrayscale(Bitmap bmpOriginal) {
int width = bmpOriginal.getWidth();
int height = bmpOriginal.getHeight();
// color information
int A, R, G, B;
int pixel;
int[][] greys = new int[width][height];
// scan through all pixels
for (int x = 0; x < width; ++x) {
for (int y = 0; y < height; ++y) {
// get pixel color
pixel = bmpOriginal.getPixel(x, y);
R = Color.red(pixel);
G = Color.green(pixel);
B = Color.blue(pixel);
int gray = (int) (0.2989 * R + 0.5870 * G + 0.1140 * B);
greys[x][y] = gray;
}
}
return greys;
}
Код для обнаружения края:
private int[][] detectEges(int[][] detectionBitmap) {
int width = detectionBitmap.length;
int height = detectionBitmap[0].length;
int[][] edges = new int[width][height];
// Loop over all pixels in the bitmap
int c1 = 0;
int c2 = 0;
for (int y = 0; y < height; y++) {
for (int x = 2; x < width -2; x++) {
// Calculate d0 for each pixel
int p0 = detectionBitmap[x][y];
int p1 = detectionBitmap[x-1][y];
int p2 = detectionBitmap[x+1][y];
int p3 = detectionBitmap[x-2][y];
int p4 = detectionBitmap[x+2][y];
int d0 = Math.abs(p1 + p2 - 2*p0) + Math.abs(p3 + p4 - 2*p0);
if(d0 >= Gamma) {
c1++;
edges[x][y] = Gamma;
} else {
c2++;
edges[x][y] = d0;
}
}
}
return edges;
}
Код для адаптивного порога. Реализация SAT взята отсюда:
private int[][] AdaptiveThreshold(int[][] detectionBitmap) {
// Create the integral image
processSummedAreaTable(detectionBitmap);
int width = detectionBitmap.length;
int height = detectionBitmap[0].length;
int[][] binaryImage = new int[width][height];
int white = 0;
int black = 0;
int h_w = 20; // The window size
int half = h_w/2;
// Loop over all pixels in the bitmap
for (int y = half; y < height - half; y++) {
for (int x = half; x < width - half; x++) {
// Calculate d0 for each pixel
int sum = 0;
for(int k = -half; k < half - 1; k++) {
for (int j = -half; j < half - 1; j++) {
sum += detectionBitmap[x + k][y + j];
}
}
if(detectionBitmap[x][y] >= (sum / (h_w * h_w)) * Beta) {
binaryImage[x][y] = 255;
white++;
} else {
binaryImage[x][y] = 0;
black++;
}
}
}
return binaryImage;
}
/**
* Process given matrix into its summed area table (in-place)
* O(MN) time, O(1) space
* @param matrix source matrix
*/
private void processSummedAreaTable(int[][] matrix) {
int rowSize = matrix.length;
int colSize = matrix[0].length;
for (int i=0; i<rowSize; i++) {
for (int j=0; j<colSize; j++) {
matrix[i][j] = getVal(i, j, matrix);
}
}
}
/**
* Helper method for processSummedAreaTable
* @param row current row number
* @param col current column number
* @param matrix source matrix
* @return sub-matrix sum
*/
private int getVal (int row, int col, int[][] matrix) {
int leftSum; // sub matrix sum of left matrix
int topSum; // sub matrix sum of top matrix
int topLeftSum; // sub matrix sum of top left matrix
int curr = matrix[row][col]; // current cell value
/* top left value is itself */
if (row == 0 && col == 0) {
return curr;
}
/* top row */
else if (row == 0) {
leftSum = matrix[row][col - 1];
return curr + leftSum;
}
/* left-most column */
if (col == 0) {
topSum = matrix[row - 1][col];
return curr + topSum;
}
else {
leftSum = matrix[row][col - 1];
topSum = matrix[row - 1][col];
topLeftSum = matrix[row - 1][col - 1]; // overlap between leftSum and topSum
return curr + leftSum + topSum - topLeftSum;
}
}
1 ответ
Марвин предлагает подход к поиску текстовых областей. Возможно, это может стать отправной точкой для вас:
Найти текстовые области в изображениях: http://marvinproject.sourceforge.net/en/examples/findTextRegions.html
Этот подход также использовался в этом вопросе:
Как мне отделить текстовую область от изображения в Java
Используя ваше изображение, я получил этот вывод:
Исходный код:
package textRegions;
import static marvin.MarvinPluginCollection.findTextRegions;
import java.awt.Color;
import java.util.List;
import marvin.image.MarvinImage;
import marvin.image.MarvinSegment;
import marvin.io.MarvinImageIO;
public class FindVehiclePlate {
public FindVehiclePlate() {
MarvinImage image = MarvinImageIO.loadImage("./res/vehicle.jpg");
image = findText(image, 30, 20, 100, 170);
MarvinImageIO.saveImage(image, "./res/vehicle_out.png");
}
public MarvinImage findText(MarvinImage image, int maxWhiteSpace, int maxFontLineWidth, int minTextWidth, int grayScaleThreshold){
List<MarvinSegment> segments = findTextRegions(image, maxWhiteSpace, maxFontLineWidth, minTextWidth, grayScaleThreshold);
for(MarvinSegment s:segments){
if(s.height >= 10){
s.y1-=20;
s.y2+=20;
image.drawRect(s.x1, s.y1, s.x2-s.x1, s.y2-s.y1, Color.red);
image.drawRect(s.x1+1, s.y1+1, (s.x2-s.x1)-2, (s.y2-s.y1)-2, Color.red);
image.drawRect(s.x1+2, s.y1+2, (s.x2-s.x1)-4, (s.y2-s.y1)-4, Color.red);
}
}
return image;
}
public static void main(String[] args) {
new FindVehiclePlate();
}
}