I need to read the received bytes and parse them with a custom method, which basically goes through bytes and skips every 0x7D (or a } ). But what happens now is that it does that for only one received packet, and I dont know why that is happening since im calling my method in didUpdateValueForCharacteristic :
- (void) peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
{
if (error)
{
NSLog(#"Error receiving notification for characteristic %#: %#", characteristic, error);
return;
}
NSLog(#"Received data on a characteristic.");
if (characteristic == self.rxCharacteristic)
{
self.bluetoothData = [[NSMutableData alloc] initWithData:[characteristic value]];
[self unstuff1:[characteristic value]];
NSLog(#"First NSDAta %#", self.bluetoothData);
[self unstuff1:self.bluetoothData];
NSLog(#"Parsed First NSDATA %#", self.bluetoothData);
NSString *stringForPrintingOutToConsole = [[NSString alloc] initWithData:self.bluetoothData encoding:NSASCIIStringEncoding];
[self.delegate didReceiveData:stringForPrintingOutToConsole];
}
else if ([characteristic.UUID isEqual:self.class.hardwareRevisionStringUUID])
{
NSString *hwRevision = #"";
const uint8_t *bytes = characteristic.value.bytes;
for (int i = 0; i < characteristic.value.length; i++)
{
NSLog(#"%x", bytes[i]);
hwRevision = [hwRevision stringByAppendingFormat:#"0x%02x, ", bytes[i]];
}
// this one reads the hardware revision
[self.delegate didReadHardwareRevisionString:[hwRevision substringToIndex:hwRevision.length-2]];
}
}
and a custom method for parsing :
- (NSMutableData *)unstuff1:(NSMutableData *)mutableData {
NSUInteger dataLength = [self.bluetoothData length];
unsigned char *bytes = [self.bluetoothData bytes];
uint16_t i, j = 0;
for (int i = 0; i < dataLength; i++)
{
if (bytes[i] == 0x7D)
{
bytes[j] = bytes[i+1] ^ 0x20;
i++;
} else
{
bytes[j] = bytes[i];
j++;
}
}
return mutableData;
}
if anyone has a clue, i'd highly appreciate help.
I am Integrating Apple Pay Integration using cyberSource in my Application .
what i have done so for .
1- I have created merchant ID
2- I have created test account on http://www.cybersource.com/
3- I have downloaded cybersource SDK for iOS.
4- I have managed to run Demo Application downloaded with SDK.
5- When i run demo App i am getting this error.
"Transaction Details .Accepted: No .Auth Amount: (null) Error: Unknown error"
I have provided my merchant account correctly . There are other fields as well which i am not sure how to get those values
static NSString* kMetadataEncodedValue = #"RklEPUNPTU1PTi5MRy5JTkFQUC5QQVlNRU5U";
static NSString* const kPaymentSolutionDefaultValue = #"001";
static NSString* const kEnvTest = #"test";
static NSString* const kEnvLive = #"live";
static NSString* const kKeyMerchantID = #"merchant_otm_eyebuy_acct";
static NSString* const kKeyMerchantExternalID = #"merchant_otm_eyebuy_acct";
static NSString* const kKeyTransactionKey = #"transactionKey";
static NSString* const kKeyEncryptedBlob = #"encryptedBlob";
static NSString* const kKeyEnv = #"env";
- (IBAction)payButtonTouchDown:(id)sender {
[self.amountTextField resignFirstResponder];
NSString* requestType = [self.requestTypeSelection titleForSegmentAtIndex:self.requestTypeSelection.selectedSegmentIndex];
[self updateStatusMessage:[NSString stringWithFormat:#"Submitting %# request...", requestType]];
self.payButton.enabled = NO;
NSString *amountText = self.amountTextField.text;
NSDecimalNumber *amountValue = [NSDecimalNumber decimalNumberWithString:amountText];
BOOL isDecimal = amountValue!= nil;
if (isDecimal) {
// TODO: Pass in encrypted payment data from PassKit
[self performRequestWithEncryptedPaymentData:self.selectedAccountData[kKeyEncryptedBlob] withPaymentAmount:amountValue];
}
else {
self.payButton.enabled = YES;
self.statusText.text = #"Enter valid Amount";
}
}
- (IBAction)requestTypeSelectionValueChanged:(UISegmentedControl *)sender {
[self updateRequestSelection];
}
-(void) updateRequestSelection {
NSString* requestType = [self.requestTypeSelection titleForSegmentAtIndex:self.requestTypeSelection.selectedSegmentIndex];
[self updateStatusMessage:[NSString stringWithFormat:#"Tap '%#' to %# request.", self.payButton.currentTitle, requestType]];
}
- (void) updateStatusMessage: (NSString*) message
{
self.statusText.text = message;
self.payButton.enabled = YES;
}
- (void)performRequestWithEncryptedPaymentData: (NSString*) encryptedPaymentData withPaymentAmount: (NSDecimalNumber*) paymentAmount
{
VMposItem *item = [[VMposItem alloc] init];
item.name = NSLocalizedString(#"Item no 1", nil);
item.price = paymentAmount;
VMposTransactionObject *transactionObject = [VMposTransactionObject createTransaction:VMPOS_TRANSACTION_PAYMENT];
[transactionObject addItem:item];
[transactionObject calculateTotals];
// TODO: Encrypted Payment is created by client application based
// on specification from SOAP API. The following values are just place holders
VMposEncryptedPayment* payment = [VMposEncryptedPayment new];
payment.encodedData = encryptedPaymentData;
payment.encodedMetadata = kMetadataEncodedValue;
payment.paymentSolution = kPaymentSolutionDefaultValue;
// Purchase details
VMposPurchaseDetails* purchaseDetails = [VMposPurchaseDetails new];
purchaseDetails.partialIndicator = NO;
// Sample Billing information
VMposAddress* billTo = [VMposAddress new];
billTo.firstName = #"John";
billTo.lastName = #"Doe";
billTo.email = #"john.doe#yahoo.com";
billTo.street1 = #"1234 Pine St.";
billTo.city = #"Redmond";
billTo.state = #"WA";
billTo.postalCode = #"98052";
billTo.country = #"US";
// Save transaction information
transactionObject.encryptedPayment = payment;
transactionObject.purchaseDetails = purchaseDetails;
transactionObject.purchaseDetails.commerceIndicator = #"internet";
transactionObject.transactionCode = #"ref_code_12345678";
transactionObject.billTo = billTo;
// Build fingerprint
//--WARNING!----------------
// Finger print generation requires the transaction key. This should
// be done at the server. It is shown here only for Demo purposes.
NSString* merchantID = self.selectedAccountData[kKeyMerchantID];
NSString* fingerprint = [self buildFingerprintWithTransaction:transactionObject withMerchantId:merchantID];
NSLog(#"Fingerprint: %#", fingerprint);
VMposGateway* gateway = [VMposGateway sharedInstance];
[gateway initSessionWithUserName:merchantID withMerchantId:merchantID withFingerprint: fingerprint withDelegate:self];
if (self.selectedAccountData[kKeyEnv] == kEnvLive) {
[VMposSettings sharedInstance].cybsEnvironment = ENV_LIVE;
}
else
{
[VMposSettings sharedInstance].cybsEnvironment = ENV_TEST;
}
if (self.requestTypeSelection.selectedSegmentIndex == 0)
{
[gateway performAuthorizationWithTransaction:transactionObject withDelegate:self];
}
else
{
[gateway performSaleWithTransaction:transactionObject withDelegate:self];
}
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self configureAccounts];
[self updateStatusMessage:[NSString stringWithFormat:#"Tap '%#' to submit a test request.", self.payButton.currentTitle]];
self.amountTextField.keyboardType = UIKeyboardTypeDecimalPad;
self.amountTextField.text = #"1.09";
[self.requestTypeSelection setSelectedSegmentIndex:0];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
// returns the number of 'columns' to display.
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView {
return 1;
}
// returns the # of rows in each component..
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component {
return self.configuredAccounts.count;
}
- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component
{
NSDictionary* rowData = self.configuredAccounts[row];
return [NSString stringWithFormat:#"%# (%#)", rowData[kKeyMerchantID], rowData[kKeyEnv]];
}
- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component {
self.selectedAccountData = self.configuredAccounts[row];
}
//! Callback for user session initialization
- (void) didInitUserSession: (VMposUserSession*) paramUserSession withError:(VMposError*)paramError {
}
//! provides feedback from finished authorization transaction request
/*!
\param paramResponseData gateway data retrieved from server (contains information about transaction status)
\param paramError an error if request failed
*/
- (void) authorizationFinishedWithGatewayResponse:(VMposGatewayResponse *)paramResponseData
withError:(VMposError *)paramError {
self.authorized = YES;
[self updateStatusMessageWithResponse: (VMposGatewayResponse *)paramResponseData withError: paramError];
}
//! provides feedback from finished sale request
/*!
\param paramResponseData gateway data retrieved from server (contains information about transaction status)
\param paramError an error if request failed
*/
- (void) saleFinishedWithGatewayResponse:(VMposGatewayResponse *)paramResponseData
withError:(VMposError *)paramError
{
self.authorized = YES;
[self updateStatusMessageWithResponse: (VMposGatewayResponse *)paramResponseData withError: paramError];
}
- (void) updateStatusMessageWithResponse: (VMposGatewayResponse *)paramResponseData withError: (NSError*) paramError
{
NSMutableString* s = [NSMutableString new];
if (paramResponseData)
{
[s appendString: #"\nTransaction Details:"];
[s appendFormat: #"\n * Accepted: %#", paramResponseData.isAccepted ? #"Yes" : #"No"];
[s appendFormat: #"\n * Auth Amount: %#", paramResponseData.authorizedAmount.stringValue];
}
if (paramError)
{
[s appendFormat:#"\nError: %#", paramError.localizedDescription];
}
[self updateStatusMessage:s];
}
/*
----------WARNING!----------------
Finger print generation requires the transaction key. This should
be done at the server. It is shown here only for Demo purposes.
*/
-(NSString*) buildFingerprintWithTransaction: (VMposTransactionObject*) transactionObject withMerchantId: (NSString*) merchantId {
NSDate* dateNow = [NSDate date];
NSString* fingerprintDateString = [MPDemoViewController formatFingerprintDate:dateNow];
NSString* merchantTransKey = self.selectedAccountData[kKeyTransactionKey];
NSString* fgComponents = [NSString stringWithFormat:#"%#%#%#%#%#", [MPDemoViewController stringSha1:merchantTransKey], merchantId, transactionObject.transactionCode, [transactionObject.totalAmount gatewayPriceString], fingerprintDateString];
NSString* hashedFgComponents = [MPDemoViewController stringHmacSha256:fgComponents];
return [NSString stringWithFormat:#"%##%#", hashedFgComponents, fingerprintDateString];
}
+(NSString*) formatFingerprintDate: (NSDate*) date {
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
NSTimeZone* tz = [NSTimeZone timeZoneWithName:#"UTC"];
[dateFormatter setDateFormat:#"yyyy-MM-dd'T'HH:mm:ss'Z'"];
[dateFormatter setTimeZone:tz];
return [dateFormatter stringFromDate:date];
}
+ (NSString *)stringSha1:(NSString *)value
{
const char *cstr = [value cStringUsingEncoding:NSUTF8StringEncoding];
NSData *data = [NSData dataWithBytes:cstr length:value.length];
uint8_t digest[CC_SHA1_DIGEST_LENGTH];
// This is an iOS5-specific method.
// It takes in the data, how much data, and then output format, which in this case is an int array.
CC_SHA1(data.bytes, (uint)data.length, digest);
return [self stringHexEncode:digest withLength:CC_SHA1_DIGEST_LENGTH];
}
+ (NSString *)stringSha256:(NSString *)value
{
const char *cstr = [value cStringUsingEncoding:NSUTF8StringEncoding];
NSData *data = [NSData dataWithBytes:cstr length:value.length];
uint8_t digest[CC_SHA256_DIGEST_LENGTH];
// This is an iOS5-specific method.
// It takes in the data, how much data, and then output format, which in this case is an int array.
CC_SHA256(data.bytes, (uint)data.length, digest);
return [self stringHexEncode:digest withLength:CC_SHA256_DIGEST_LENGTH];
}
+ (NSString *)stringHmacSha256:(NSString *)value
{
CCHmacContext ctx;
const char* utf8ValueString = [value UTF8String];
uint8_t hmacData[CC_SHA256_DIGEST_LENGTH];
CCHmacInit(&ctx, kCCHmacAlgSHA256, utf8ValueString, strlen(utf8ValueString));
CCHmacUpdate(&ctx, utf8ValueString, strlen(utf8ValueString));
CCHmacFinal(&ctx, hmacData);
return [self stringHexEncode:hmacData withLength:CC_SHA256_DIGEST_LENGTH];
}
+(NSString*) stringHexEncode: (uint8_t*) data withLength: (NSInteger) dataLength {
NSMutableString* output = [NSMutableString stringWithCapacity:dataLength * 2];
// Parse through the CC_SHA256 results (stored inside of digest[]).
for(int i = 0; i < dataLength; i++) {
[output appendFormat:#"%02x", data[i]];
}
return output;
}
+ (NSString*)base64forData:(NSData*)theData {
const uint8_t* input = (const uint8_t*)[theData bytes];
NSInteger length = [theData length];
static char table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
NSMutableData* data = [NSMutableData dataWithLength:((length + 2) / 3) * 4];
uint8_t* output = (uint8_t*)data.mutableBytes;
NSInteger i;
for (i=0; i < length; i += 3) {
NSInteger value = 0;
NSInteger j;
for (j = i; j < (i + 3); j++) {
value <<= 8;
if (j < length) {
value |= (0xFF & input[j]);
}
}
NSInteger theIndex = (i / 3) * 4;
output[theIndex + 0] = table[(value >> 18) & 0x3F];
output[theIndex + 1] = table[(value >> 12) & 0x3F];
output[theIndex + 2] = (i + 1) < length ? table[(value >> 6) & 0x3F] : '=';
output[theIndex + 3] = (i + 2) < length ? table[(value >> 0) & 0x3F] : '=';
}
return [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding] ;
}
From where i can get all these values . it will be highly appreciated if some one in list all steps involved in Apple pay integration through CyberSource.
Also Please do not refer me to apple developer as i have read out details about Apple Pay on Apple Developer.
i am developing app related to bluetooth my iOS device act as a central and watch act as a peripheral.In order to get some response from the watch i need to give some commands to the watch so that it gives info back i have written the following code
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:
(CBCharacteristic *)characteristic error:(NSError *)error
{
NSString *characteristicUUIDstring = TRANSFER_CHARACTERISTIC_UUID;
CBUUID *characteristicUUID = [CBUUID
UUIDWithString:characteristicUUIDstring];
int comm[6];
comm[0]=0x01;
comm[1]=6;
comm[2]=0x53;
comm[3]=0x00;
comm[4]=0xFFFF;
comm[5]=0xFFFF;
NSMutableArray *arr=[[NSMutableArray alloc]initWithCapacity:4];
for (int i = 0; i < 4; i++) {
NSNumber *number = [NSNumber numberWithFloat:comm[i]];
[arr addObject:number];
}
NSLog(#"mutable arr is %#",arr);
NSString *error1;
NSData *dataarr = [NSPropertyListSerialization dataFromPropertyList:arr
format:NSPropertyListBinaryFormat_v1_0 errorDescription:&error1];
NSUInteger len = [dataarr length];
Byte *byteData = (Byte*)malloc(len);
memcpy(byteData, [dataarr bytes], len);
short crc = (short) 0xFFFF;
for (int j = 0; j < [arr count]; j++)
{
Byte c = arr[j];
for (int i = 7; i >= 0; i--)
{
BOOL c15 = ((crc >> 15 & 1) == 1);
BOOL bit = ((c >> (7 - i) & 1) == 1);
crc <<= 1;
if (c15 ^ bit)
{
crc ^= 0x1021; }
}
}
int crc2 = crc - 0xffff0000;
byteData[0] = (Byte) (crc2 % 256);
byteData[1] = (Byte) (crc2 / 256);
NSLog(#"byte data 1is %hhu",byteData[0]);
NSLog(#"byte data 2is %hhu",byteData[1]);
int com[6];
com[0]=0x01;
com[1]=6;
com[2]=0x53;
com[3]=0x00;
com[4]=byteData[0];
com[5]=byteData[1];
NSMutableArray *sendarr=[[NSMutableArray alloc]initWithCapacity:6];
for (int i = 0; i < 6; i++) {
NSNumber *number = [NSNumber numberWithFloat:comm[i]];
[sendarr addObject:number];
}
NSLog(#"mutable arr is %#",sendarr);
// NSData *data = [NSData datawith:&dataByte length:1];
CBMutableCharacteristic *testCharacteristic =
[[CBMutableCharacteristic alloc] initWithType:characteristicUUID
properties:CBCharacteristicPropertyRead|CBCharacteristicPropertyWrite
value:sendarr
permissions:CBAttributePermissionsReadable|CBAttributePermissionsWriteable];
NSLog(#"Read or Write %# ",testCharacteristic);
[peripheral writeValue:dataarr forCharacteristic:testCharacteristic
type:CBCharacteristicWriteWithResponse];
}
I am trying to store text data as compressed blobs.
[db executeUpdate:#"create table blobTable (a text, b blob)"];
[db executeUpdate:#"insert into blobTable (a, b) values (?, compressMe('random text string'))", #"lord of the rings"];
And then I try to query them using:
FMResultSet *rs = [db executeQuery:#"select uncompressMe(b) as k from blobTable where a = ?", #"lord of the rings"];
Where compressMe and uncompressMe are defined as:
FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:dbPath];
[queue inDatabase:^(FMDatabase *adb) {
[adb makeFunctionNamed:#"compressMe" maximumArguments:1 withBlock:^(sqlite3_context *context, int aargc, sqlite3_value **aargv) {
if (sqlite3_value_type(aargv[0]) == SQLITE_TEXT) {
#autoreleasepool {
const char *c = (const char *)sqlite3_value_text(aargv[0]);
NSString *s = [NSString stringWithUTF8String:c];
NSLog(#"string to compress: %#", s);
NSData *compressedBody = [self gzipDeflate:[s dataUsingEncoding:NSUTF8StringEncoding]];
sqlite3_result_blob(context, (__bridge const void *)(compressedBody), [compressedBody length], nil);
}
}
else {
NSLog(#"Unknown formart for StringStartsWithH (%d) %s:%d", sqlite3_value_type(aargv[0]), __FUNCTION__, __LINE__);
sqlite3_result_null(context);
}
}];
}];
[queue inDatabase:^(FMDatabase *adb) {
[adb makeFunctionNamed:#"uncompressMe" maximumArguments:1 withBlock:^(sqlite3_context *context, int aargc, sqlite3_value **aargv) {
if (sqlite3_value_type(aargv[0]) == SQLITE_BLOB) {
#autoreleasepool {
NSLog(#"inside uncompressMe");
NSUInteger len = sqlite3_value_bytes(aargv[0]);
Byte *byteData = (Byte*)malloc(len);
memcpy(byteData, sqlite3_value_blob(aargv[0]), len);
NSData *data = [[NSData alloc] initWithBytes:byteData length:len];
NSData *deflatedBody = [self gzipInflate:data];
NSString *deflatedString = [[NSString alloc] initWithData:deflatedBody encoding:NSUTF8StringEncoding];
sqlite3_result_text(context, (__bridge const void *)(deflatedString), -1, SQLITE_UTF8);
}
}
else {
NSLog(#"Unknown formart for StringStartsWithH (%d) %s:%d", sqlite3_value_type(aargv[0]), __FUNCTION__, __LINE__);
sqlite3_result_null(context);
}
}];
}];
For the sake of completeness, the zip functions are defined as such:
- (NSData *)gzipInflate:(NSData*)data
{
if ([data length] == 0) return data;
unsigned full_length = [data length];
unsigned half_length = [data length] / 2;
NSMutableData *decompressed = [NSMutableData dataWithLength: full_length + half_length];
BOOL done = NO;
int status;
z_stream strm;
strm.next_in = (Bytef *)[data bytes];
strm.avail_in = [data length];
strm.total_out = 0;
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
if (inflateInit2(&strm, (15+32)) != Z_OK) return nil;
while (!done)
{
// Make sure we have enough room and reset the lengths.
if (strm.total_out >= [decompressed length])
[decompressed increaseLengthBy: half_length];
strm.next_out = [decompressed mutableBytes] + strm.total_out;
strm.avail_out = [decompressed length] - strm.total_out;
// Inflate another chunk.
status = inflate (&strm, Z_SYNC_FLUSH);
if (status == Z_STREAM_END) done = YES;
else if (status != Z_OK) break;
}
if (inflateEnd (&strm) != Z_OK) return nil;
// Set real length.
if (done)
{
[decompressed setLength: strm.total_out];
return [NSData dataWithData: decompressed];
}
else return nil;
}
- (NSData *)gzipDeflate:(NSData*)data
{
if ([data length] == 0) return data;
z_stream strm;
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
strm.opaque = Z_NULL;
strm.total_out = 0;
strm.next_in=(Bytef *)[data bytes];
strm.avail_in = [data length];
// Compresssion Levels:
// Z_NO_COMPRESSION
// Z_BEST_SPEED
// Z_BEST_COMPRESSION
// Z_DEFAULT_COMPRESSION
if (deflateInit2(&strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, (15+16), 8, Z_DEFAULT_STRATEGY) != Z_OK) return nil;
NSMutableData *compressed = [NSMutableData dataWithLength:16384]; // 16K chunks for expansion
do {
if (strm.total_out >= [compressed length])
[compressed increaseLengthBy: 16384];
strm.next_out = [compressed mutableBytes] + strm.total_out;
strm.avail_out = [compressed length] - strm.total_out;
deflate(&strm, Z_FINISH);
} while (strm.avail_out == 0);
deflateEnd(&strm);
[compressed setLength: strm.total_out];
return [NSData dataWithData:compressed];
}
However, the uncompressMe function doesnt seem to succeed. It fails in the Inflate method, which always returns a nil. Any thoughts what I might be doing wrong? I have checked to make sure that the blob column does get populated.
I feel like issue is from below code:
NSUInteger len = sqlite3_value_bytes(aargv[0]);
Byte *byteData = (Byte*)malloc(len);
memcpy(byteData, sqlite3_value_blob(aargv[0]), len);
NSData *data = [[NSData alloc] initWithBytes:byteData length:len];
Try this to load NSData from database instead of creating byte variable and copying it.
const void *bytes = sqlite3_column_blob(statement, 3);
NSData *data = [[NSData alloc] initWithBytes:bytes length:size];
When I use an image taken with the camera on the iPhone and send it to tesseract the accuracy is horrible it is all garbage text, but when I choose the same image from the photo library I get great accuracy.
How can I improve tesseracts accuracy from a picture take with the camera? Here is what I am doing to the image before sending
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {
UIImage *image = info[UIImagePickerControllerOriginalImage];
CGFloat newWidth = 1200;
CGSize newSize = CGSizeMake(newWidth, newWidth);
image = [image resizedImage:newSize interpolationQuality:kCGInterpolationHigh];
Tesseract* tesseract = [[Tesseract alloc]initWithLanguage:#"eng"];
[tesseract setVariableValue:#"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ##&*()-_:." forKey:#"tessedit_char_whitelist"];
[tesseract setVariableValue:#"`~!%^*[]{}<>?|" forKey:#"tessedit_char_blacklist"];
[tesseract setImage:image];
[tesseract recognize];
NSLog(#"%#", [tesseract recognizedText]);
[picker dismissViewControllerAnimated:YES completion:NULL];
}
If anyone has found some "magical" way to get tesseract to scan with great accuracy I will reward a bounty!
Main things to consider:
Tesseract needs to be trained for the font and language being recognized.
It looks like you are supplying appropriate parameters here - are they the same that you use when you supply a predefined image?
Tesseract doesn't orientate, or 'clean up' images
When you pass an image to the library, it should already be cleaned up and in portrait. Is the
image in photo reel taken from the camera too, or is it a sample image you've saved there somehow?
One check to make is the resize function:
CGFloat newWidth = 1200;
CGSize newSize = CGSizeMake(newWidth, newWidth);
The original image will be distorted by this, since you are resizing a non-square image into a square image. This will definitely squash the text and make life difficult for Tesseract. At the very least, you want to preserve the aspect ratio of the captured image.
My code work fine, but it´s more complex
- (IBAction)captureTapped:(id)sender
{
NSMutableArray *results = [NSMutableArray array];
NSString *fullWord = #"";
OCRImplementation *ocr = [[OCRImplementation alloc] init];
for(int j = 0; j < [self.images count]; j++){
UIImage *imageToTesseract = [self.images objectAtIndex:j];
//UIImage *imageToTesseract = self.imgfinal.image;
NSMutableArray *sortedKeys = [NSMutableArray array];
#try {
sortedKeys = [ocr processImageDetectText:imageToTesseract threadhold:198];
}
#catch (NSException *exception) {
sortedKeys = [NSMutableArray array];
}
NSString *finalWord = #"";
if([sortedKeys count] > 0){
for(int i=0; i<[sortedKeys count]; i++){
UIImage *image = [sortedKeys objectAtIndex:i];
finalWord = [self confidencesOCRTesseract:image];
if(finalWord.length > 1){
finalWord = [NSString stringWithFormat:#"%c",[finalWord characterAtIndex:0]];
}
fullWord = [fullWord stringByAppendingString:finalWord];
}
}
fullWord = [fullWord stringByReplacingOccurrencesOfString:#"\n" withString:#""];
[results addObject:fullWord];
NSLog(#"-- RESULT -- %#",fullWord);
}
NSString *resultWord = #"";
if([results count] > 0){
resultWord = [self calculateStatics:results];
}
//Your text Result
NSLog(#"%#",resultWord);
}
- (NSString*)calculateStatics:(NSMutableArray*)results{
NSMutableArray *first = [NSMutableArray array];
NSMutableArray *second = [NSMutableArray array];
NSMutableArray *third = [NSMutableArray array];
NSMutableArray *fourth = [NSMutableArray array];
NSMutableArray *fifth = [NSMutableArray array];
NSMutableArray *six = [NSMutableArray array];
NSMutableArray *seven = [NSMutableArray array];
for(int i = 0; i<[results count]; i++){
NSString *result = [results objectAtIndex:i];
if(result && ![result isEqualToString:#""]){
if(result.length >= 1 && [result characterAtIndex:0]){
[first addObject:[NSString stringWithFormat:#"%c", [result characterAtIndex:0]]];
}else{
[first addObject:#" "];
}
if(result.length >= 2 &&[result characterAtIndex:1]){
[second addObject:[NSString stringWithFormat:#"%c", [result characterAtIndex:1]]];
}else{
[second addObject:#" "];
}
if(result.length >= 3 &&[result characterAtIndex:2]){
[third addObject:[NSString stringWithFormat:#"%c", [result characterAtIndex:2]]];
}else{
[third addObject:#" "];
}
if(result.length >= 4 &&[result characterAtIndex:3]){
[fourth addObject:[NSString stringWithFormat:#"%c", [result characterAtIndex:3]]];
}else{
[fourth addObject:#" "];
}
if(result.length >= 5 &&[result characterAtIndex:4]){
[fifth addObject:[NSString stringWithFormat:#"%c", [result characterAtIndex:4]]];
}else{
[fifth addObject:#" "];
}
if(result.length >= 6 &&[result characterAtIndex:5]){
[six addObject:[NSString stringWithFormat:#"%c", [result characterAtIndex:5]]];
}else{
[six addObject:#" "];
}
if(result.length >= 7 &&[result characterAtIndex:6]){
[seven addObject:[NSString stringWithFormat:#"%c", [result characterAtIndex:6]]];
}else{
[seven addObject:#" "];
}
}else{
[first addObject:#" "];
[second addObject:#" "];
[third addObject:#" "];
[fourth addObject:#" "];
[fifth addObject:#" "];
[six addObject:#" "];
[seven addObject:#" "];
}
}
NSString *word = #"";
NSCountedSet *frequencies = [NSCountedSet setWithArray:first];
if([frequencies count] == 1){
word = [word stringByAppendingString:[[frequencies allObjects] objectAtIndex:0]];
}else{
NSUInteger count = 0;
NSString *repeatedWord = #"";
for(int i=0; i<[frequencies count]; i++){
NSString *possibleWord = [[frequencies allObjects] objectAtIndex:i];
NSUInteger wordCount = [frequencies countForObject:possibleWord];
if(count < wordCount){
count = wordCount;
repeatedWord = possibleWord;
}
}
word = [word stringByAppendingString:repeatedWord];
}
NSCountedSet *frequencies2 = [NSCountedSet setWithArray:second];
if([frequencies2 count] == 1){
word = [word stringByAppendingString:[[frequencies2 allObjects] objectAtIndex:0]];
}else{
NSUInteger count = 0;
NSString *repeatedWord = #"";
for(int i=0; i<[frequencies2 count]; i++){
NSString *possibleWord = [[frequencies2 allObjects] objectAtIndex:i];
NSUInteger wordCount = [frequencies2 countForObject:possibleWord];
if(count < wordCount){
count = wordCount;
repeatedWord = possibleWord;
}
}
word = [word stringByAppendingString:repeatedWord];
}
NSCountedSet *frequencies3 = [NSCountedSet setWithArray:third];
if([frequencies3 count] == 1){
word = [word stringByAppendingString:[[frequencies3 allObjects] objectAtIndex:0]];
}else{
NSUInteger count = 0;
NSString *repeatedWord = #"";
for(int i=0; i<[frequencies3 count]; i++){
NSString *possibleWord = [[frequencies3 allObjects] objectAtIndex:i];
NSUInteger wordCount = [frequencies3 countForObject:possibleWord];
if(count < wordCount){
count = wordCount;
repeatedWord = possibleWord;
}
}
word = [word stringByAppendingString:repeatedWord];
}
NSCountedSet *frequencies4 = [NSCountedSet setWithArray:fourth];
if([frequencies4 count] == 1){
word = [word stringByAppendingString:[[frequencies4 allObjects] objectAtIndex:0]];
}else{
NSUInteger count = 0;
NSString *repeatedWord = #"";
for(int i=0; i<[frequencies4 count]; i++){
NSString *possibleWord = [[frequencies4 allObjects] objectAtIndex:i];
NSUInteger wordCount = [frequencies4 countForObject:possibleWord];
if(count < wordCount){
count = wordCount;
repeatedWord = possibleWord;
}
}
word = [word stringByAppendingString:repeatedWord];
}
NSCountedSet *frequencies5 = [NSCountedSet setWithArray:fifth];
if([frequencies5 count] == 1){
word = [word stringByAppendingString:[[frequencies5 allObjects] objectAtIndex:0]];
}else{
NSUInteger count = 0;
NSString *repeatedWord = #"";
for(int i=0; i<[frequencies5 count]; i++){
NSString *possibleWord = [[frequencies5 allObjects] objectAtIndex:i];
NSUInteger wordCount = [frequencies5 countForObject:possibleWord];
if(count < wordCount){
count = wordCount;
repeatedWord = possibleWord;
}
}
word = [word stringByAppendingString:repeatedWord];
}
NSCountedSet *frequencies6 = [NSCountedSet setWithArray:six];
if([frequencies6 count] == 1){
word = [word stringByAppendingString:[[frequencies6 allObjects] objectAtIndex:0]];
}else{
NSUInteger count = 0;
NSString *repeatedWord = #"";
for(int i=0; i<[frequencies6 count]; i++){
NSString *possibleWord = [[frequencies6 allObjects] objectAtIndex:i];
NSUInteger wordCount = [frequencies6 countForObject:possibleWord];
if(count < wordCount){
count = wordCount;
repeatedWord = possibleWord;
}
}
word = [word stringByAppendingString:repeatedWord];
}
NSCountedSet *frequencies7 = [NSCountedSet setWithArray:seven];
if([frequencies7 count] == 1){
word = [word stringByAppendingString:[[frequencies7 allObjects] objectAtIndex:0]];
}else{
NSUInteger count = 0;
NSString *repeatedWord = #"";
for(int i=0; i<[frequencies7 count]; i++){
NSString *possibleWord = [[frequencies7 allObjects] objectAtIndex:i];
NSUInteger wordCount = [frequencies7 countForObject:possibleWord];
if(count < wordCount){
count = wordCount;
repeatedWord = possibleWord;
}
}
word = [word stringByAppendingString:repeatedWord];
}
return word;
}
OCRImplementation class
OCRImplementation.h
#ifndef __TesseractSample__OCRImplementation__
#define __TesseractSample__OCRImplementation__
#endif /* defined(__TesseractSample__OCRImplementation__) */
#interface OCRImplementation : NSObject{
}
- (UIImage*)processImage:(id)sender;
- (NSString*)confidencesOCRTesseract:(UIImage*)picture;
#end
OCRImplementation.mm
#include "OCRImplementation.h"
#import <OpenCV/opencv2/imgproc/imgproc.hpp>
#import <OpenCV/opencv2/highgui/highgui.hpp>
#import "UIImage+OpenCV.h"
#import "Tesseract.h"
#import "baseapi.h"
#import "environ.h"
#import "pix.h"
#include <sstream>
#include <iostream>
#include <vector>
#include "OpenCV/opencv2/core/core.hpp"
#include "OpenCV/opencv2/features2d/features2d.hpp"
#include "OpenCV/opencv2/calib3d/calib3d.hpp"
#implementation OCRImplementation
- (NSMutableArray*)processImageDetectText:(id)sender threadhold:(int)threadhold{
UIImage *img1 = sender;
cv::Mat src = [img1 CVMat];
cv::Mat src_gray;
cv::Mat threshold_output;
cv::vector<cv::vector<cv::Point> > contours;
cv::vector<cv::Vec4i> hierarchy;
int thresh = 100;
cv::RNG rng(12345);
/// Convert image to gray and blur it
cvtColor( src, src_gray, CV_BGR2GRAY );
blur( src_gray, src_gray, cv::Size(3,3) );
/// Detect edges using Threshold
cv::threshold( src_gray, threshold_output, thresh, 255, cv::THRESH_BINARY );
/// Find contours
cv::findContours( threshold_output, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE, cv::Point(0, 0) );
/// Approximate contours to polygons + get bounding rects and circles
cv::vector<cv::vector<cv::Point> > contours_poly( contours.size() );
cv::vector<cv::Rect> boundRect( contours.size() );
cv::vector<cv::Point2f>center( contours.size() );
cv::vector<float>radius( contours.size() );
for( int i = 0; i < contours.size(); i++ )
{
approxPolyDP( cv::Mat(contours[i]), contours_poly[i], 3, true );
boundRect[i] = boundingRect( cv::Mat(contours_poly[i]) );
minEnclosingCircle( (cv::Mat)contours_poly[i], center[i], radius[i] );
}
/// Draw polygonal contour + bonding rects + circles
cv::Mat drawing = cv::Mat::zeros( threshold_output.size(), CV_8UC3 );
NSMutableDictionary *dictionaryImages = [NSMutableDictionary dictionary];
NSMutableArray *areaArray = [NSMutableArray array];
float lastArea = 0.0;
for( int i = 0; i< contours.size(); i++ )
{
cv::Scalar color = cv::Scalar( rng.uniform(0, 255), rng.uniform(0,255), rng.uniform(0,255) );
if(boundRect[i].height > 50){
double area = cv::contourArea(contours[i]);
if((boundRect[i].width < boundRect[i].height) && area > (lastArea / 2)){
lastArea = area;
[areaArray addObject:[NSString stringWithFormat:#"%f",area]];
rectangle( drawing, boundRect[i].tl(), boundRect[i].br(), color, 2, 8, 0 );
cv::Rect extendedRect = cv::Rect(boundRect[i].x - 5, boundRect[i].y - 5, boundRect[i].width + 10, boundRect[i].height + 10);
cv::Mat source = src;
cv::Mat target(extendedRect.size(), source.type());
if(0 <= extendedRect.x && 0 <= extendedRect.width && extendedRect.x + extendedRect.width <= source.cols && 0 <= extendedRect.y && 0 <= extendedRect.height && extendedRect.y + extendedRect.height <= source.rows){
source(extendedRect).copyTo(target);
//converting the original image into grayscale
cv::cvtColor(target, target, CV_BGR2GRAY);
cv::multiply(target, cv::Scalar(2,2,2), target);
cv::add(target, cv::Scalar(2,2,2), target);
/// Detect edges using Threshold
cv::threshold( target, threshold_output, threadhold, 255, cv::THRESH_BINARY );
cv::dilate(threshold_output, threshold_output, NULL);
UIImage *imgFinal = [OCRImplementation imageWithCVMat:threshold_output];
[dictionaryImages setObject:imgFinal forKey:[NSString stringWithFormat:#"%f", area]];
}else{
NSLog(#"Error al leer la imagen. NO ROI");
}
}
}
}
NSMutableDictionary *finalImages = [NSMutableDictionary dictionary];
NSMutableArray *sortedKeys = [NSMutableArray arrayWithArray:[areaArray sortedArrayUsingFunction:intSort context:NULL]];
for( int k = 0; k< contours.size(); k++ )
{
if(boundRect[k].height > 50){
for(int i = 0; i < [sortedKeys count]; i++){
double area = cv::contourArea(contours[k]);
if(area == [[sortedKeys objectAtIndex:i] floatValue]){
[finalImages setObject:[dictionaryImages objectForKey:[sortedKeys objectAtIndex:i]] forKey:[NSString stringWithFormat:#"%d",boundRect[k].x]];
}
}
}
}
NSMutableArray *array = [NSMutableArray array];
NSArray *keys = [finalImages allKeys];
NSArray *sortedKeys2 = [keys sortedArrayUsingFunction:intSortDesc context:NULL];
for(int i=0; i<[sortedKeys2 count]; i++){
[array addObject:[finalImages objectForKey:[sortedKeys2 objectAtIndex:i]]];
}
return array;
}
+ (UIImage *)imageWithCVMat:(const cv::Mat&)cvMat
{
NSData *data = [NSData dataWithBytes:cvMat.data length:cvMat.elemSize() * cvMat.total()];
CGColorSpaceRef colorSpace;
if (cvMat.elemSize() == 1) {
colorSpace = CGColorSpaceCreateDeviceGray();
} else {
colorSpace = CGColorSpaceCreateDeviceRGB();
}
CGDataProviderRef provider = CGDataProviderCreateWithCFData((CFDataRef)data);
CGImageRef imageRef = CGImageCreate(cvMat.cols, // Width
cvMat.rows, // Height
8, // Bits per component
8 * cvMat.elemSize(), // Bits per pixel
cvMat.step[0], // Bytes per row
colorSpace, // Colorspace
kCGImageAlphaNone | kCGBitmapByteOrderDefault, // Bitmap info flags
provider, // CGDataProviderRef
NULL, // Decode
false, // Should interpolate
kCGRenderingIntentDefault); // Intent
UIImage *image = [UIImage imageWithCGImage:imageRef];
CGImageRelease(imageRef);
CGDataProviderRelease(provider);
CGColorSpaceRelease(colorSpace);
return image;
}
- (NSString*)confidencesOCRTesseract:(UIImage*)picture{
tesseract::TessBaseAPI* tess;
uint32_t* _pixels;
NSString* _dataPath = #"tessdata";
NSString* _language = #"eng";
// Useful paths
NSFileManager *fileManager = [NSFileManager defaultManager];
NSArray *documentPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentPath = ([documentPaths count] > 0) ? [documentPaths objectAtIndex:0] : nil;
NSString *dataPath = [documentPath stringByAppendingPathComponent:_dataPath];
// Copy data in Doc Directory
if (![fileManager fileExistsAtPath:dataPath]) {
NSString *bundlePath = [[NSBundle mainBundle] bundlePath];
NSString *tessdataPath = [bundlePath stringByAppendingPathComponent:_dataPath];
if (tessdataPath) {
[fileManager copyItemAtPath:tessdataPath toPath:dataPath error:nil];
}
}
setenv("TESSDATA_PREFIX", [[documentPath stringByAppendingString:#"/"] UTF8String], 1);
tess = new tesseract::TessBaseAPI();
tess->Init([_dataPath UTF8String], [_language UTF8String]);
tess->SetVariable("save_blob_choices", "T");
tess->SetVariable("tessedit_char_whitelist", "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789");
//SET IMAGE
CGSize size = [picture size];
int width = size.width;
int height = size.height;
_pixels = (uint32_t *) malloc(width * height * sizeof(uint32_t));
// Clear the pixels so any transparency is preserved
memset(_pixels, 0, width * height * sizeof(uint32_t));
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
// Create a context with RGBA _pixels
CGContextRef context = CGBitmapContextCreate(_pixels, width, height, 8, width * sizeof(uint32_t), colorSpace,
kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedLast);
// Paint the bitmap to our context which will fill in the _pixels array
CGContextDrawImage(context, CGRectMake(0, 0, width, height), [picture CGImage]);
// We're done with the context and color space
CGContextRelease(context);
CGColorSpaceRelease(colorSpace);
tess->SetImage((const unsigned char *) _pixels, width, height, sizeof(uint32_t), width * sizeof(uint32_t));
//END SET IMAGE
tess->Recognize(NULL);
tesseract::ResultIterator* ri = tess->GetIterator();
tesseract::ChoiceIterator* ci;
// For every identified symbol (there should be only one...)
NSString *finalWord = #"";
if(ri != 0) {
do {
const char* symbol = ri->GetUTF8Text(tesseract::RIL_SYMBOL);
if(symbol != 0) {
float conf = ri->Confidence(tesseract::RIL_SYMBOL);
if(conf > 80.0f){
finalWord = [finalWord stringByAppendingString:[NSString stringWithUTF8String:symbol]];
}else{
const tesseract::ResultIterator itr = *ri;
ci = new tesseract::ChoiceIterator(itr);
// For every chosen candidate...
do {
const char* choice = ci->GetUTF8Text();
NSString *choiceStr = [NSString stringWithUTF8String:choice];
if(choice && ![choiceStr isEqualToString:#""]){
finalWord = [finalWord stringByAppendingString:choiceStr];
break;
}else{
finalWord = [finalWord stringByAppendingString:[NSString stringWithUTF8String:symbol]];
}
} while(ci->Next());
delete ci;
}
}
delete[] symbol;
} while((ri->Next(tesseract::RIL_SYMBOL)));
}
return finalWord;
}
NSInteger intSort(id num1, id num2, void *context) {
NSString *n1 = (NSString *) num1;
NSString *n2 = (NSString *) num2;
n1 = [[n1 componentsSeparatedByString:#"."] objectAtIndex:0];
n2 = [[n2 componentsSeparatedByString:#"."] objectAtIndex:0];
if ([n1 floatValue] > [n2 floatValue]) {
return NSOrderedAscending;
}
else if ([n1 floatValue] < [n2 floatValue]) {
return NSOrderedDescending;
}
return NSOrderedSame;
}
NSInteger intSortDesc(id num1, id num2, void *context) {
NSString *n1 = (NSString *) num1;
NSString *n2 = (NSString *) num2;
n1 = [[n1 componentsSeparatedByString:#"."] objectAtIndex:0];
n2 = [[n2 componentsSeparatedByString:#"."] objectAtIndex:0];
if ([n1 floatValue] < [n2 floatValue]) {
return NSOrderedAscending;
}
else if ([n1 floatValue] > [n2 floatValue]) {
return NSOrderedDescending;
}
return NSOrderedSame;
}
#end