2020-08-03 21:04:52 +03:00

363 lines
12 KiB
Objective-C

#import "PGRectangleDetector.h"
#import "LegacyComponentsInternal.h"
#import <Vision/Vision.h>
#import <CoreImage/CoreImage.h>
#import <SSignalKit/SSignalKit.h>
@interface PGRectangle ()
- (instancetype)initWithRectangleFeature:(CIRectangleFeature *)rectangleFeature;
- (instancetype)initWithRectangleObservation:(VNRectangleObservation *)rectangleObservation API_AVAILABLE(ios(11.0));
- (CGFloat)size;
@end
@interface PGRectangleEntry : NSObject
@property (nonatomic, readonly) PGRectangle *rectangle;
@property (nonatomic, assign) NSInteger rate;
- (instancetype)initWithRectangle:(PGRectangle *)rectangle;
@end
@implementation PGRectangleEntry
- (instancetype)initWithRectangle:(PGRectangle *)rectangle
{
self = [super init];
if (self != nil)
{
_rectangle = rectangle;
_rate = 0;
}
return self;
}
@end
@implementation PGRectangle
- (instancetype)initWithRectangleFeature:(CIRectangleFeature *)rectangleFeature
{
self = [super init];
if (self != nil) {
_topLeft = rectangleFeature.topLeft;
_topRight = rectangleFeature.topRight;
_bottomLeft = rectangleFeature.bottomLeft;
_bottomRight = rectangleFeature.bottomRight;
}
return self;
}
- (instancetype)initWithRectangleObservation:(VNRectangleObservation *)rectangleObservation API_AVAILABLE(ios(11.0))
{
self = [super init];
if (self != nil) {
_topLeft = rectangleObservation.topLeft;
_topRight = rectangleObservation.topRight;
_bottomLeft = rectangleObservation.bottomLeft;
_bottomRight = rectangleObservation.bottomRight;
}
return self;
}
- (PGRectangle *)transform:(CGAffineTransform)transform
{
PGRectangle *rectangle = [[PGRectangle alloc] init];
rectangle->_topLeft = CGPointApplyAffineTransform(_topLeft, transform);
rectangle->_topRight = CGPointApplyAffineTransform(_topRight, transform);
rectangle->_bottomLeft = CGPointApplyAffineTransform(_bottomLeft, transform);
rectangle->_bottomRight = CGPointApplyAffineTransform(_bottomRight, transform);
return rectangle;
}
- (PGRectangle *)rotate90
{
PGRectangle *rectangle = [[PGRectangle alloc] init];
rectangle->_topLeft = CGPointMake(_topLeft.y, _topLeft.x);
rectangle->_topRight = CGPointMake(_topRight.y, _topRight.x);
rectangle->_bottomLeft = CGPointMake(_bottomLeft.y, _bottomLeft.x);
rectangle->_bottomRight = CGPointMake(_bottomRight.y, _bottomRight.x);
return rectangle;
}
- (PGRectangle *)sort
{
NSArray *points = @[ [NSValue valueWithCGPoint:_topLeft], [NSValue valueWithCGPoint:_topRight], [NSValue valueWithCGPoint:_bottomLeft], [NSValue valueWithCGPoint:_bottomRight] ];
NSArray *ySorted = [points sortedArrayUsingComparator:^NSComparisonResult(id firstObject, id secondObject) {
CGPoint firstPoint = [firstObject CGPointValue];
CGPoint secondPoint = [secondObject CGPointValue];
if (firstPoint.y < secondPoint.y) {
return NSOrderedAscending;
} else {
return NSOrderedDescending;
}
}];
NSArray *top = [ySorted subarrayWithRange:NSMakeRange(0, 2)];
NSArray *bottom = [ySorted subarrayWithRange:NSMakeRange(2, 2)];
NSArray *xSortedTop = [top sortedArrayUsingComparator:^NSComparisonResult(id firstObject, id secondObject) {
CGPoint firstPoint = [firstObject CGPointValue];
CGPoint secondPoint = [secondObject CGPointValue];
if (firstPoint.x < secondPoint.x) {
return NSOrderedAscending;
} else {
return NSOrderedDescending;
}
}];
NSArray *xSortedBottom = [bottom sortedArrayUsingComparator:^NSComparisonResult(id firstObject, id secondObject) {
CGPoint firstPoint = [firstObject CGPointValue];
CGPoint secondPoint = [secondObject CGPointValue];
if (firstPoint.x < secondPoint.x) {
return NSOrderedAscending;
} else {
return NSOrderedDescending;
}
}];
PGRectangle *rectangle = [[PGRectangle alloc] init];
rectangle->_topLeft = [xSortedTop[0] CGPointValue];
rectangle->_topRight = [xSortedTop[1] CGPointValue];
rectangle->_bottomLeft = [xSortedBottom[0] CGPointValue];
rectangle->_bottomRight = [xSortedBottom[1] CGPointValue];
return rectangle;
}
- (PGRectangle *)cartesian:(CGFloat)height
{
PGRectangle *rectangle = [[PGRectangle alloc] init];
rectangle->_topLeft = CGPointMake(_topLeft.x, height - _topLeft.y);
rectangle->_topRight = CGPointMake(_topRight.x, height - _topRight.y);
rectangle->_bottomLeft = CGPointMake(_bottomLeft.x, height - _bottomLeft.y);
rectangle->_bottomRight = CGPointMake(_bottomRight.x, height - _bottomRight.y);
return rectangle;
}
- (PGRectangle *)normalize:(CGSize)size
{
return [self transform:CGAffineTransformMakeScale(1.0 / size.width, 1.0 / size.height)];
}
+ (CGFloat)distance:(CGPoint)a to:(CGPoint)b
{
return hypot(a.x - b.x, a.y - b.y);
}
- (CGFloat)size
{
CGFloat sum = 0.0f;
sum += [PGRectangle distance:self.topLeft to:self.topRight];
sum += [PGRectangle distance:self.topRight to:self.bottomRight];
sum += [PGRectangle distance:self.bottomRight to:self.bottomLeft];
sum += [PGRectangle distance:self.bottomLeft to:self.topLeft];
return sum;
}
+ (CGRect)pointSquare:(CGPoint)point size:(CGFloat)size
{
return CGRectMake(point.x - size / 2.0, point.y - size / 2.0, size, size);
}
- (bool)matches:(PGRectangle *)other threshold:(CGFloat)threshold
{
if (!CGRectContainsPoint([PGRectangle pointSquare:self.topLeft size:threshold], other.topLeft))
return false;
if (!CGRectContainsPoint([PGRectangle pointSquare:self.topRight size:threshold], other.topRight))
return false;
if (!CGRectContainsPoint([PGRectangle pointSquare:self.bottomLeft size:threshold], other.bottomLeft))
return false;
if (!CGRectContainsPoint([PGRectangle pointSquare:self.bottomRight size:threshold], other.bottomRight))
return false;
return true;
}
@end
@implementation PGRectangleDetector
{
SQueue *_queue;
CIDetector *_detector;
bool _disabled;
CGSize _imageSize;
NSInteger _notFoundCount;
NSMutableArray *_rectangles;
PGRectangle *_detectedRectangle;
NSInteger _autoscanCount;
}
- (instancetype)init
{
self = [super init];
if (self != nil) {
_queue = [[SQueue alloc] init];
_rectangles = [[NSMutableArray alloc] init];
}
return self;
}
- (void)updateEntries
{
for (PGRectangleEntry *entry in _rectangles) {
entry.rate = 1;
}
for (NSInteger i = 0; i < _rectangles.count; i++) {
for (NSInteger j = 0; i < _rectangles.count; i++) {
if (j > i && [[_rectangles[i] rectangle] matches:_rectangles[j] threshold:40.0]) {
((PGRectangleEntry *)_rectangles[i]).rate += 1;
((PGRectangleEntry *)_rectangles[j]).rate += 1;
}
}
}
}
- (void)addRectangle:(PGRectangle *)rectangle
{
if (_disabled)
return;
PGRectangleEntry *entry = [[PGRectangleEntry alloc] initWithRectangle:rectangle];
[_rectangles addObject:entry];
if (_rectangles.count < 3)
return;
if (_rectangles.count > 8)
[_rectangles removeObjectAtIndex:0];
[self updateEntries];
__block PGRectangleEntry *best = nil;
[_rectangles enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(PGRectangleEntry *rectangle, NSUInteger idx, BOOL * stop) {
if (best == nil) {
best = rectangle;
return;
}
if (rectangle.rate > best.rate) {
best = rectangle;
} else if (rectangle.rate == best.rate) {
if (_detectedRectangle != nil) {
if ([rectangle.rectangle matches:_detectedRectangle threshold:40.0]) {
best = rectangle;
}
}
}
}];
if (_detectedRectangle != nil && [best.rectangle matches:_detectedRectangle threshold:24.0f]) {
_autoscanCount += 1;
_detectedRectangle = best.rectangle;
if (_autoscanCount > 20) {
_autoscanCount = 0;
self.update(true, [_detectedRectangle normalize:_imageSize]);
_detectedRectangle = nil;
_disabled = true;
TGDispatchAfter(2.0, _queue._dispatch_queue, ^{
_disabled = false;
});
}
} else {
_autoscanCount = 0;
_detectedRectangle = best.rectangle;
self.update(false, [_detectedRectangle normalize:_imageSize]);
}
}
- (void)processRectangle:(PGRectangle *)rectangle imageSize:(CGSize)imageSize
{
_imageSize = imageSize;
if (rectangle != nil) {
_notFoundCount = 0;
[self addRectangle:rectangle];
} else {
_notFoundCount += 1;
if (_notFoundCount > 3) {
_autoscanCount = 0;
_detectedRectangle = nil;
self.update(false, nil);
}
}
}
- (void)detectRectangle:(CVPixelBufferRef)pixelBuffer
{
CGSize size = CGSizeMake(CVPixelBufferGetWidth(pixelBuffer), CVPixelBufferGetHeight(pixelBuffer));
if (@available(iOS 11.0, *)) {
CVPixelBufferRetain(pixelBuffer);
NSError *error;
VNImageRequestHandler *handler = [[VNImageRequestHandler alloc] initWithCVPixelBuffer:pixelBuffer options:@{}];
VNDetectRectanglesRequest *request = [[VNDetectRectanglesRequest alloc] initWithCompletionHandler:^(VNRequest * _Nonnull request, NSError * _Nullable error) {
CVPixelBufferRelease(pixelBuffer);
[_queue dispatch:^{
if (error == nil && request.results.count > 0) {
PGRectangle *largestRectangle = nil;
for (VNRectangleObservation *result in request.results) {
if (![result isKindOfClass:[VNRectangleObservation class]])
continue;
PGRectangle *rectangle = [[PGRectangle alloc] initWithRectangleObservation:result];
if (largestRectangle == nil || largestRectangle.size < rectangle.size) {
largestRectangle = rectangle;
}
}
[self processRectangle:[largestRectangle transform:CGAffineTransformMakeScale(size.width, size.height)] imageSize:size];
} else {
[self processRectangle:nil imageSize:size];
}
}];
}];
request.minimumConfidence = 0.85f;
request.maximumObservations = 15;
request.minimumAspectRatio = 0.33;
request.minimumSize = 0.4;
[handler performRequests:@[request] error:&error];
} else {
CVPixelBufferRetain(pixelBuffer);
[_queue dispatch:^{
if (_detector == nil) {
_detector = [CIDetector detectorOfType:CIDetectorTypeRectangle context:[CIContext contextWithOptions:nil] options:@{ CIDetectorAccuracy: CIDetectorAccuracyHigh }];
}
CIImage *image = [[CIImage alloc] initWithCVPixelBuffer:pixelBuffer];
NSArray *results = [_detector featuresInImage:image];
CVPixelBufferRelease(pixelBuffer);
PGRectangle *largestRectangle = nil;
for (CIRectangleFeature *result in results) {
if (![result isKindOfClass:[CIRectangleFeature class]])
continue;
PGRectangle *rectangle = [[PGRectangle alloc] initWithRectangleFeature:result];
if (largestRectangle == nil || largestRectangle.size < rectangle.size) {
largestRectangle = rectangle;
}
}
[self processRectangle:largestRectangle imageSize:size];
}];
}
}
@end