/*************************************************************************** copyright : (C) 2007 by Sebastian Held email : sebastian.held@gmx.de ***************************************************************************/ /*************************************************************************** * * * ### based on BaToo: http://people.inf.ethz.ch/adelmanr/batoo/ ### * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of version 2 of the GNU General Public License as * * published by the Free Software Foundation; * * * ***************************************************************************/ // includes code from http://v4l2spec.bytesex.org/spec/a16323.htm #include #include #include "barcode.h" #include using barcodeRecognition::barcodeRecognitionThread; using barcodeRecognition::Barcode_EAN13; using barcodeRecognition::Decoder_EAN13; using barcodeRecognition::MatchMakerResult; barcodeRecognitionThread::barcodeRecognitionThread() { m_stop = false; m_barcode_v4l = new barcode_v4l(); } barcodeRecognitionThread::~barcodeRecognitionThread() { delete m_barcode_v4l; } bool barcodeRecognitionThread::isWebcamAvailable() { return m_barcode_v4l->isOpen(); } void barcodeRecognitionThread::run() { bool stop; m_stop_mutex.lock(); stop = m_stop; m_stop_mutex.unlock(); if (!isWebcamAvailable()) stop = true; Barcode_EAN13 old; while (!stop) { QImage img; m_barcode_img_mutex.lock(); if (m_barcode_img.isNull()) img = m_barcode_v4l->grab_one2(); else { img = m_barcode_img; m_barcode_img = QImage(); } m_barcode_img_mutex.unlock(); // //DEBUG // img.load( "/home/sebastian/black.png" ); // m_stop = true; // //DEBUG if (!img.isNull()) { QImage preview = img.scale( 320, 240, QImage::ScaleMin ); emit gotImage( preview ); Barcode_EAN13 barcode = recognize( img ); if (barcode.isValid() && (old != barcode)) { emit recognized( barcode.toString() ); old = barcode; } } msleep( 10 ); // reduce load m_stop_mutex.lock(); stop = m_stop; m_stop_mutex.unlock(); } } void barcodeRecognitionThread::stop() { // attention! This function is called from GUI context m_stop_mutex.lock(); m_stop = true; m_stop_mutex.unlock(); } void barcodeRecognitionThread::recognizeBarcode( QImage img ) { // attention! This function is called from GUI context m_barcode_img_mutex.lock(); m_barcode_img = img; m_barcode_img_mutex.unlock(); } Barcode_EAN13 barcodeRecognitionThread::recognize( QImage img ) { // PARAMETERS: int amount_scanlines = 30; int w = img.width(); int h = img.height(); // the array which will contain the result: QValueVector< QValueVector > numbers( amount_scanlines, QValueVector(13,-1) ); // no init in java source!!!!!!!!! // generate and initialize the array that will contain all detected // digits at a specific code position: int possible_numbers[10][13][2]; for (int i = 0; i < 10; i++) { for (int j = 0; j < 13; j++) { possible_numbers[i][j][0] = -1; possible_numbers[i][j][1] = 0; } } int successfull_lines = 0; // try to detect the barcode along scanlines: for (int i = 0; i < amount_scanlines; i++) { int x1 = 0; int y = (h / amount_scanlines) * i; int x2 = w - 1; // try to recognize a barcode along that path: Barcode_EAN13 ean13_code = recognizeCode( img, x1, x2, y ); numbers[i] = ean13_code.getNumbers(); if (ean13_code.isValid()) { successfull_lines++; // add the recognized digits to the array of possible numbers: addNumberToPossibleNumbers( numbers[i], possible_numbers, true ); } else { numbers[i] = ean13_code.getNumbers(); // add the recognized digits to the array of possible numbers: addNumberToPossibleNumbers(numbers[i], possible_numbers, false); } #ifdef BarcodeDecoder_DEBUG // show the information that has been recognized along the scanline: qDebug( "Scanline %i result: %s\n", i, ean13_code.toString().latin1() ); #endif } // sort the detected digits at each code position, in accordance to the // amount of their detection: sortDigits(possible_numbers); #ifdef BarcodeDecoder_DEBUG fprintf( stderr, "detected digits:\n" ); printArray( possible_numbers, 0 ); fprintf( stderr, "# of their occurence:\n" ); printArray( possible_numbers, 1 ); #endif // get the most likely barcode: Barcode_EAN13 code = extractBarcode(possible_numbers); return code; } void barcodeRecognitionThread::printArray( int array[10][13][2], int level ) { for (int i = 0; i < 10; i++) { QCString temp; temp.setNum( i ); fprintf( stderr, temp + " : " ); for (int j = 0; j < 13; j++) { if (array[i][j][level] == -1) fprintf( stderr, "x " ); else { QCString temp; temp.setNum( array[i][j][level] ); fprintf( stderr, temp + " " ); } } fprintf( stderr, "\n" ); } } barcodeRecognition::Barcode_EAN13 barcodeRecognitionThread::recognizeCode( QImage img, int x1, int x2, int y ) { QValueVector raw_path(x2-x1+1); for (int x=x1; x<=x2; x++) raw_path[x-x1] = img.pixel(x,y); // convert the given path into a string of black and white pixels: QValueVector string = transformPathToBW( raw_path ); // convert the string of black&white pixels into a list, containing // information about the black and white fields // first indes = field nr. // second index: 0 = color of the field // 1 = field length QValueVector< QValueVector > fields = extractFieldInformation( string ); // try to recognize a EAN13 code: Barcode_EAN13 barcode = Decoder_EAN13::recognize( fields ); return barcode; } void barcodeRecognitionThread::addNumberToPossibleNumbers( QValueVector number, int possible_numbers[10][13][2], bool correct_code ) { int i; bool digit_contained; for (int j = 0; j < 13; j++) { if (number[j] >= 0) { i = 0; digit_contained = false; while ((i < 10) && (possible_numbers[i][j][0] >= 0)) { if (possible_numbers[i][j][0] == number[j]) { digit_contained = true; if (correct_code) possible_numbers[i][j][1] = possible_numbers[i][j][1] + 100; else possible_numbers[i][j][1]++; break; } i++; } if ((i < 10) && (!digit_contained)) { // add new digit: possible_numbers[i][j][0] = number[j]; if (correct_code) possible_numbers[i][j][1] = possible_numbers[i][j][1] + 100; else possible_numbers[i][j][1]++; } } } } void barcodeRecognitionThread::sortDigits( int possible_numbers[10][13][2] ) { int i; int temp_value; int temp_occurence; bool changes; for (int j = 0; j < 13; j++) { i = 1; changes = false; while (true) { if ((possible_numbers[i - 1][j][0] >= 0) && (possible_numbers[i][j][0] >= 0)) { if (possible_numbers[i - 1][j][1] < possible_numbers[i][j][1]) { temp_value = possible_numbers[i - 1][j][0]; temp_occurence = possible_numbers[i - 1][j][1]; possible_numbers[i - 1][j][0] = possible_numbers[i][j][0]; possible_numbers[i - 1][j][1] = possible_numbers[i][j][1]; possible_numbers[i][j][0] = temp_value; possible_numbers[i][j][1] = temp_occurence; changes = true; } } if ((possible_numbers[i][j][0] < 0) || (i >= 9)) { if (!changes) break; else { i = 1; changes = false; } } else i++; } } } Barcode_EAN13 barcodeRecognitionThread::extractBarcode( int possible_numbers[10][13][2] ) { // create and initialize the temporary variables: QValueVector temp_code(13); for (int i = 0; i < 13; i++) temp_code[i] = possible_numbers[0][i][0]; #ifdef Barcode_DEBUG fprintf( stderr, "barcodeRecognitionThread::extractBarcode(): " ); for (int i=0; i<13; i++) fprintf( stderr, "%i", temp_code[i] ); fprintf( stderr, "\n" ); #endif return Barcode_EAN13(temp_code); } Barcode_EAN13 barcodeRecognitionThread::detectValidBarcode ( int possible_numbers[10][13][2], int max_amount_of_considered_codes ) { // create and initialize the temporary variables: QValueVector temp_code(13); for ( int i = 0; i < 13; i++ ) temp_code[i] = possible_numbers[0][i][0]; int alternative_amount = 0; QValueVector counter( 13 ); // no init in java source!!! int counter_nr = 11; // check if there is at least one complete code present: for ( int i = 0; i < 13; i++ ) { // exit and return the "most likely" code parts: if ( temp_code[i] < 0 ) return Barcode_EAN13( temp_code ); } // if there is at least one complete node, try to detect a valid barcode: while ( alternative_amount < max_amount_of_considered_codes ) { // fill the temporary code array with one possible version: for ( int i = 0; i < 13; i++ ) temp_code[i] = possible_numbers[counter[i]][i][0]; alternative_amount++; // check if this version represents a valid code: if (isValid( temp_code )) return Barcode_EAN13( temp_code ); // increment the counters: if ( ( counter[counter_nr] < 9 ) && ( possible_numbers[counter[counter_nr] + 1][counter_nr][0] >= 0 ) ) { // increment the actual counter. counter[counter_nr]++; } else { // check if we have reached the end and no valid barcode has been found: if ( counter_nr == 1 ) { // exit and return the "most likely" code parts: for ( int i = 0; i < 13; i++ ) temp_code[i] = possible_numbers[0][i][0]; return Barcode_EAN13( temp_code ); } else { // reset the actual counter and increment the next one(s): counter[counter_nr] = 0; while ( true ) { if ( counter_nr > 2 ) counter_nr--; else { for ( int i = 0; i < 13; i++ ) temp_code[i] = possible_numbers[0][i][0]; return Barcode_EAN13( temp_code ); } if ( counter[counter_nr] < 9 ) { counter[counter_nr]++; if ( possible_numbers[counter[counter_nr]][counter_nr][0] < 0 ) counter[counter_nr] = 0; else break; } else counter[counter_nr] = 0; } counter_nr = 12; } } } for ( int i = 0; i < 13; i++ ) temp_code[i] = possible_numbers[0][i][0]; return Barcode_EAN13( temp_code ); } bool barcodeRecognitionThread::isValid( int numbers[13] ) { QValueVector temp(13); for (int i=0; i<13; i++) temp[i] = numbers[i]; return isValid( temp ); } bool barcodeRecognitionThread::isValid( QValueVector numbers ) { Q_ASSERT( numbers.count() == 13 ); // calculate the checksum of the barcode: int sum1 = numbers[0] + numbers[2] + numbers[4] + numbers[6] + numbers[8] + numbers[10]; int sum2 = 3 * (numbers[1] + numbers[3] + numbers[5] + numbers[7] + numbers[9] + numbers[11]); int checksum_value = sum1 + sum2; int checksum_digit = 10 - (checksum_value % 10); if (checksum_digit == 10) checksum_digit = 0; #ifdef Barcode_DEBUG fprintf( stderr, "barcodeRecognitionThread::isValid(): " ); for (int i=0; i<13; i++) fprintf( stderr, "%i", numbers[i] ); fprintf( stderr, "\n" ); #endif return (numbers[12] == checksum_digit); } QValueVector barcodeRecognitionThread::transformPathToBW( QValueVector line ) { int w = line.count(); QValueVector bw_line(w,0); bw_line[0] = 255; // create greyscale values: QValueVector grey_line(w,0); int average_illumination = 0; for (int x = 0; x < w; x++) { grey_line[x] = (qRed(line.at(x)) + qGreen(line.at(x)) + qBlue(line.at(x))) / 3; average_illumination = average_illumination + grey_line[x]; } average_illumination = average_illumination / w; // perform the binarization: int range = w / 20; // temp values: int moving_sum; int moving_average; int v1_index = -range + 1; int v2_index = range; int v1 = grey_line[0]; int v2 = grey_line[range]; int current_value; int comparison_value; // initialize the moving sum: moving_sum = grey_line[0] * range; for (int i = 0; i < range; i++) moving_sum = moving_sum + grey_line[i]; // apply the adaptive thresholding algorithm: for (int i = 1; i < w - 1; i++) { if (v1_index > 0) v1 = grey_line[v1_index]; if (v2_index < w) v2 = grey_line[v2_index]; else v2 = grey_line[w - 1]; moving_sum = moving_sum - v1 + v2; moving_average = moving_sum / (range << 1); v1_index++; v2_index++; current_value = (grey_line[i - 1] + grey_line[i]) >> 1; // decide if the current pixel should be black or white: comparison_value = (3 * moving_average + average_illumination) >> 2; if ((current_value < comparison_value - 3)) bw_line[i] = 0; else bw_line[i] = 255; } // filter the values: (remove too small fields) if (w >= 640) { for (int x = 1; x < w - 1; x++) { if ((bw_line[x] != bw_line[x - 1]) && (bw_line[x] != bw_line[x + 1])) bw_line[x] = bw_line[x - 1]; } } QValueVector ret(w,0); for (int i=0; i > barcodeRecognitionThread::extractFieldInformation( QValueVector string ) { QValueVector< QValueVector > temp_fields( string.count(), QValueVector(2,0) ); if (string.count() == 0) return QValueVector< QValueVector >(); int field_counter = 0; int last_value = string.at(0); int last_fields = 1; for (uint i = 1; i < string.size(); i++) { if ((string.at(i) == last_value) && (i < string.size() - 1)) { last_fields++; } else { // create new field entry: temp_fields[field_counter][0] = last_value; temp_fields[field_counter][1] = last_fields; last_value = string.at(i); last_fields = 0; field_counter++; } } temp_fields.resize( field_counter ); #ifdef Barcode_DEBUG fprintf( stderr, "barcodeRecognitionThread::extractFieldInformation(): " ); for (int i=0; i code ) { setCode( code ); } //ok void Barcode_EAN13::setCode( QValueVector code ) { if (code.count() != 13) { m_numbers.clear(); m_numbers.resize(13,-1); m_null = true; return; } m_numbers = code; m_null = false; } //ok bool Barcode_EAN13::isValid() const { if (m_null) return false; for (int i = 0; i < 13; i++) if ((m_numbers[i] < 0) || (m_numbers[i] > 9)) return false; // calculate the checksum of the barcode: int sum1 = m_numbers[0] + m_numbers[2] + m_numbers[4] + m_numbers[6] + m_numbers[8] + m_numbers[10]; int sum2 = 3 * (m_numbers[1] + m_numbers[3] + m_numbers[5] + m_numbers[7] + m_numbers[9] + m_numbers[11]); int checksum_value = sum1 + sum2; int checksum_digit = 10 - (checksum_value % 10); if (checksum_digit == 10) checksum_digit = 0; return (m_numbers[12] == checksum_digit); } //ok QValueVector Barcode_EAN13::getNumbers() const { return m_numbers; } //ok QString Barcode_EAN13::toString() const { QString s; for (int i = 0; i < 13; i++) if ((m_numbers[i] >= 0) && (m_numbers[i] <= 9)) s += QString::number(m_numbers[i]); else s += '?'; return s; } //ok bool Barcode_EAN13::operator!= ( const Barcode_EAN13 &code ) { if (m_null != code.m_null) return true; if (!m_null) for (int i=0; i<13; i++) if (m_numbers[i] != code.m_numbers[i]) return true; return false; } //ok Barcode_EAN13 Decoder_EAN13::recognize( QValueVector< QValueVector > fields ) { // try to extract the encoded information from the field series: QValueVector numbers = decode( fields, 0, fields.count() ); Barcode_EAN13 barcode( numbers ); // return the results: return barcode; } QValueVector Decoder_EAN13::decode( QValueVector< QValueVector > fields, int start_i, int end_i ) { // determine the length of the path in pixels int length = 0; for (uint i = 0; i < fields.size(); i++) length += fields.at(i).at(1); // set the parameters accordingly: int max_start_sentry_bar_differences; int max_unit_length; int min_unit_length; if (length <= 800) { max_start_sentry_bar_differences = 6; max_unit_length = 10; min_unit_length = 1; } else { max_start_sentry_bar_differences = 30; max_unit_length = 50; min_unit_length = 1; } // consistency checks: if (fields.count() <= 0) return QValueVector(); if (start_i > end_i - 3) return QValueVector(); if (end_i - start_i < 30) return QValueVector(); // (just a rough value) // relevant indexes: int start_sentinel_i; int end_sentinel_i; int left_numbers_i; int middle_guard_i; int right_numbers_i; // relevant parameters: float unit_length; // results: QValueVector numbers( 13, -1 ); // the java source does no initialization // determine the relevant positions: // Try to detect the start sentinel (a small black-white-black serie): start_sentinel_i = -1; for (int i = start_i; i < end_i - 56; i++) { if (fields[i][0] == 0) { if ((fields[i][1] >= min_unit_length) && (fields[i][1] <= max_unit_length)) { if ((abs(fields[i][1] - fields[i + 1][1]) <= max_start_sentry_bar_differences) && (abs(fields[i][1] - fields[i + 2][1]) <= max_start_sentry_bar_differences) && (fields[i + 3][1] < fields[i][1] << 3)) { start_sentinel_i = i; break; } } } } #ifdef Decoder_EAN13_DEBUG fprintf( stderr, "start_sentinal_index: %i\n", start_sentinel_i ); #endif if (start_sentinel_i < 0) return QValueVector(); // calculate the other positions: left_numbers_i = start_sentinel_i + 3; middle_guard_i = left_numbers_i + 6 * 4; right_numbers_i = middle_guard_i + 5; end_sentinel_i = right_numbers_i + 6 * 4; if (end_sentinel_i + 3 > end_i) return QValueVector(); // calculate the average (pixel) length of a bar that is one unit wide: // (a complete barcode consists out of 95 length units) int temp_length = 0; int field_amount = (end_sentinel_i - start_sentinel_i + 3); for (int i = start_sentinel_i; i < start_sentinel_i + field_amount; i++) temp_length += fields[i][1]; unit_length = (float) ((float) temp_length / 95.0f); #ifdef Decoder_EAN13_DEBUG fprintf( stderr, "unit_width: %f\n", unit_length ); #endif QValueVector< QValueVector > current_number_field( 4, QValueVector(2,0) ); if (left_numbers_i + 1 > end_i) return QValueVector(); // test the side from which we are reading the barcode: for (int j = 0; j < 4; j++) { current_number_field[j][0] = fields[left_numbers_i + j][0]; current_number_field[j][1] = fields[left_numbers_i + j][1]; } MatchMakerResult matchMakerResult = recognizeNumber( current_number_field, BOTH_TABLES ); if (matchMakerResult.isEven()) { // we are reading the barcode from the back side: // use the already obtained information: numbers[12] = matchMakerResult.getDigit(); // try to recognize the "right" numbers: int counter = 11; for (int i = left_numbers_i + 4; i < left_numbers_i + 24; i = i + 4) { for (int j = 0; j < 4; j++) { current_number_field[j][0] = fields[i + j][0]; current_number_field[j][1] = fields[i + j][1]; } matchMakerResult = recognizeNumber(current_number_field, EVEN_TABLE); numbers[counter] = matchMakerResult.getDigit(); counter--; } bool parity_pattern[6]; // true = even, false = odd //(counter has now the value 6) // try to recognize the "left" numbers: for (int i = right_numbers_i; i < right_numbers_i + 24; i = i + 4) { for (int j = 0; j < 4; j++) { current_number_field[j][0] = fields[i + j][0]; current_number_field[j][1] = fields[i + j][1]; } matchMakerResult = recognizeNumber(current_number_field, BOTH_TABLES); numbers[counter] = matchMakerResult.getDigit(); parity_pattern[counter-1] = !matchMakerResult.isEven(); counter--; } // try to determine the system code: matchMakerResult = recognizeSystemCode(parity_pattern); numbers[0] = matchMakerResult.getDigit(); } else { // we are reading the barcode from the "correct" side: bool parity_pattern[6]; // true = even, false = odd // use the already obtained information: numbers[1] = matchMakerResult.getDigit(); parity_pattern[0] = matchMakerResult.isEven(); // try to recognize the left numbers: int counter = 2; for (int i = left_numbers_i + 4; i < left_numbers_i + 24; i = i + 4) { for (int j = 0; j < 4; j++) { current_number_field[j][0] = fields[i + j][0]; current_number_field[j][1] = fields[i + j][1]; } matchMakerResult = recognizeNumber(current_number_field, BOTH_TABLES); numbers[counter] = matchMakerResult.getDigit(); parity_pattern[counter-1] = matchMakerResult.isEven(); counter++; } // try to determine the system code: matchMakerResult = recognizeSystemCode(parity_pattern); numbers[0] = matchMakerResult.getDigit(); // try to recognize the right numbers: counter = 0; for (int i = right_numbers_i; i < right_numbers_i + 24; i = i + 4) { for (int j = 0; j < 4; j++) { current_number_field[j][0] = fields[i + j][0]; current_number_field[j][1] = fields[i + j][1]; } matchMakerResult = recognizeNumber(current_number_field, ODD_TABLE); numbers[counter + 7] = matchMakerResult.getDigit(); counter++; } } return numbers; } MatchMakerResult Decoder_EAN13::recognizeNumber( QValueVector< QValueVector > fields, int code_table_to_use) { // convert the pixel lenghts of the four black&white fields into // normed values that have together a length of 70; int pixel_sum = fields[0][1] + fields[1][1] + fields[2][1] + fields[3][1]; int b[4]; for (int i = 0; i < 4; i++) { b[i] = round((((float) fields[i][1]) / ((float) pixel_sum)) * 70); } #ifdef Decoder_EAN13_DEBUG fprintf( stderr, "Recognize Number (code table to use: %i):\n", code_table_to_use ); fprintf( stderr, "lengths: %i %i %i %i\n", fields[0][1], fields[1][1], fields[2][1], fields[3][1] ); fprintf( stderr, "normed lengths: %i %i %i %i\n", b[0], b[1], b[2], b[3] ); #endif // try to detect the digit that is encoded by the set of four normed bar lenghts: int max_difference_for_acceptance = 60; int temp; int even_min_difference = 100000; int even_min_difference_index = 0; int odd_min_difference = 100000; int odd_min_difference_index = 0; if ((code_table_to_use == BOTH_TABLES)||(code_table_to_use == EVEN_TABLE)) { QValueVector even_differences(10,0); for (int i = 0; i < 10; i++) { for (int j = 0; j < 4; j++) { // calculate the differences in the even group: temp = b[j] - code_even[i][j]; if (temp < 0) even_differences[i] = even_differences[i] + ((-temp) << 1); else even_differences[i] = even_differences[i] + (temp << 1); } if (even_differences[i] < even_min_difference) { even_min_difference = even_differences[i]; even_min_difference_index = i; } } } if ((code_table_to_use == BOTH_TABLES) || (code_table_to_use == ODD_TABLE)) { QValueVector odd_differences(10,0); for (int i = 0; i < 10; i++) { for (int j = 0; j < 4; j++) { // calculate the differences in the odd group: temp = b[j] - code_odd[i][j]; if (temp < 0) odd_differences[i] = odd_differences[i] + ((-temp) << 1); else odd_differences[i] = odd_differences[i] + (temp << 1); } if (odd_differences[i] < odd_min_difference) { odd_min_difference = odd_differences[i]; odd_min_difference_index = i; } } } // select the digit and parity with the lowest difference to the found pattern: if (even_min_difference <= odd_min_difference) { if (even_min_difference < max_difference_for_acceptance) return MatchMakerResult( true, even_min_difference_index ); } else { if (odd_min_difference < max_difference_for_acceptance) return MatchMakerResult( false, odd_min_difference_index ); } return MatchMakerResult( false, -1 ); } MatchMakerResult Decoder_EAN13::recognizeSystemCode( bool parity_pattern[6] ) { // search for a fitting parity pattern: bool fits = false; for (int i = 0; i < 10; i++) { fits = true; for (int j = 0; j < 6; j++) { if (parity_pattern_list[i][j] != parity_pattern[j]) { fits = false; break; } } if (fits) return MatchMakerResult( false, i ); } return MatchMakerResult( false, -1 ); } //ok MatchMakerResult::MatchMakerResult( bool even, int digit ) { m_even = even; m_digit = digit; } #include "barcode.moc"