Skip to content

Commit ec7221f

Browse files
committed
demo(ios): ios demo using opencv in c++
1 parent d5b2080 commit ec7221f

File tree

13 files changed

+711
-3
lines changed

13 files changed

+711
-3
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+
<plist version="1.0">
4+
<dict>
5+
<key>NSCameraUsageDescription</key>
6+
<string>we use the camera cause we want to!</string>
7+
</dict>
8+
</plist>
Lines changed: 279 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,279 @@
1+
#include "./include/DocumentDetector.h"
2+
3+
using namespace detector;
4+
using namespace cv;
5+
using namespace std;
6+
7+
typedef std::pair<std::vector<cv::Point>, double> PointAndArea;
8+
bool sortByArea(PointAndArea contour1, PointAndArea contour2)
9+
{
10+
return (contour1.second > contour2.second);
11+
}
12+
13+
DocumentDetector::DocumentDetector(cv::Mat &bitmap, int resizeThreshold, int imageRotation) {
14+
image = bitmap;
15+
DocumentDetector::resizeThreshold = resizeThreshold;
16+
DocumentDetector::imageRotation = imageRotation;
17+
}
18+
19+
DocumentDetector::DocumentDetector(int resizeThreshold, int imageRotation) {
20+
DocumentDetector::resizeThreshold = resizeThreshold;
21+
DocumentDetector::imageRotation = imageRotation;
22+
}
23+
24+
DocumentDetector::~DocumentDetector() {
25+
}
26+
27+
28+
double angle(cv::Point pt1, cv::Point pt2, cv::Point pt0) {
29+
double dx1 = pt1.x - pt0.x;
30+
double dy1 = pt1.y - pt0.y;
31+
double dx2 = pt2.x - pt0.x;
32+
double dy2 = pt2.y - pt0.y;
33+
return (dx1 * dx2 + dy1 * dy2) /
34+
sqrt((dx1 * dx1 + dy1 * dy1) * (dx2 * dx2 + dy2 * dy2) + 1e-10);
35+
}
36+
37+
void DocumentDetector::findSquares(cv::Mat srcGray, double scaledWidth, double scaledHeight,
38+
std::vector<std::pair<std::vector<cv::Point>, double>> &squares) {
39+
// Contours search
40+
std::vector<std::vector<cv::Point>> contours;
41+
vector<Vec4i> hierarchy;
42+
cv::findContours(srcGray, contours, hierarchy, cv::RETR_TREE, cv::CHAIN_APPROX_SIMPLE);
43+
44+
std::vector<Point> approx;
45+
for (size_t i = 0; i < contours.size(); i++) {
46+
std::vector<Point> contour = contours[i];
47+
48+
// Detection of geometric shapes
49+
double epsilon = cv::arcLength(contour, true) * contoursApproxEpsilonFactor;
50+
cv::approxPolyDP(contour, approx, epsilon, true);
51+
52+
// Detection of quadrilaterals among geometric shapes
53+
if (approx.size() >= 4 && cv::isContourConvex(approx)) {
54+
const double area = std::abs(contourArea(approx));
55+
if (area > scaledWidth / areaScaleMinFactor * (scaledHeight / areaScaleMinFactor)) {
56+
double maxCosine = 0.0;
57+
for (int j = 2; j < 5; j++) {
58+
double cosine = std::abs(angle(approx[j % 4], approx[j - 2], approx[j - 1]));
59+
maxCosine = std::max(maxCosine, cosine);
60+
}
61+
// Selection of quadrilaterals with large enough angles
62+
if (maxCosine < 0.5) {
63+
squares.push_back(std::pair<std::vector<cv::Point>, double>(approx, area));
64+
}
65+
}
66+
}
67+
}
68+
}
69+
70+
vector<vector<cv::Point>> DocumentDetector::scanPoint() {
71+
Mat edged;
72+
vector<vector<cv::Point>> result = scanPoint(edged);
73+
edged.release();
74+
return result;
75+
}
76+
77+
long long DocumentDetector::pointSideLine(Point &lineP1, Point &lineP2, Point &point) {
78+
long x1 = lineP1.x;
79+
long y1 = lineP1.y;
80+
long x2 = lineP2.x;
81+
long y2 = lineP2.y;
82+
long x = point.x;
83+
long y = point.y;
84+
return (x - x1)*(y2 - y1) - (y - y1)*(x2 - x1);
85+
}
86+
vector<cv::Point> DocumentDetector::sortPointClockwise(vector<cv::Point> points) {
87+
if (points.size() != 4) {
88+
return points;
89+
}
90+
91+
Point unFoundPoint;
92+
vector<Point> result = {unFoundPoint, unFoundPoint, unFoundPoint, unFoundPoint};
93+
94+
long minDistance = -1;
95+
for(Point &point : points) {
96+
long distance = point.x * point.x + point.y * point.y;
97+
if(minDistance == -1 || distance < minDistance) {
98+
result[0] = point;
99+
minDistance = distance;
100+
}
101+
}
102+
if (result[0] != unFoundPoint) {
103+
Point &leftTop = result[0];
104+
points.erase(std::remove(points.begin(), points.end(), leftTop));
105+
if ((pointSideLine(leftTop, points[0], points[1]) * pointSideLine(leftTop, points[0], points[2])) < 0) {
106+
result[2] = points[0];
107+
} else if ((pointSideLine(leftTop, points[1], points[0]) * pointSideLine(leftTop, points[1], points[2])) < 0) {
108+
result[2] = points[1];
109+
} else if ((pointSideLine(leftTop, points[2], points[0]) * pointSideLine(leftTop, points[2], points[1])) < 0) {
110+
result[2] = points[2];
111+
}
112+
}
113+
if (result[0] != unFoundPoint && result[2] != unFoundPoint) {
114+
Point &leftTop = result[0];
115+
Point &rightBottom = result[2];
116+
points.erase(std::remove(points.begin(), points.end(), rightBottom));
117+
if (pointSideLine(leftTop, rightBottom, points[0]) > 0) {
118+
result[1] = points[0];
119+
result[3] = points[1];
120+
} else {
121+
result[1] = points[1];
122+
result[3] = points[0];
123+
}
124+
}
125+
126+
if (result[0] != unFoundPoint && result[1] != unFoundPoint && result[2] != unFoundPoint && result[3] != unFoundPoint) {
127+
return result;
128+
}
129+
130+
return points;
131+
}
132+
vector<vector<cv::Point>> DocumentDetector::scanPoint(Mat &edged) {
133+
double width;
134+
double height;
135+
Mat image = resizeImage();
136+
cvtColor(image, image, COLOR_BGR2GRAY);
137+
// convert photo to LUV colorspace to avoid glares caused by lights
138+
if (imageRotation != 0) {
139+
switch (imageRotation) {
140+
case 90:
141+
rotate(image, image, ROTATE_90_CLOCKWISE);
142+
break;
143+
case 180:
144+
rotate(image, image, ROTATE_180);
145+
break;
146+
default:
147+
rotate(image, image, ROTATE_90_COUNTERCLOCKWISE);
148+
break;
149+
150+
}
151+
}
152+
// cvtColor(image, image, COLOR_BGR2Luv);
153+
Size size = image.size();
154+
width = size.width;
155+
height = size.height;
156+
cv::Mat blurred;
157+
158+
cv::GaussianBlur(image, image, cv::Size(9,9), 0);
159+
// dilate helps to remove potential holes between edge segments
160+
Mat kernel = cv::getStructuringElement(MORPH_RECT,cv::Size(9,9));
161+
cv::morphologyEx(image, image, MORPH_CLOSE, kernel);
162+
cv::Canny(image, image, 0, 84);
163+
// cv::Mat threshOutput = cv::Mat(blurred.size(), CV_8U);
164+
std::vector<PointAndArea> squares;
165+
std::vector<PointAndArea> foundSquares;
166+
std::vector<int> indices;
167+
// for (int c = 2; c >= 0; c--) {
168+
// Mat lBlurred[] = {blurred};
169+
// Mat lOutput[] = {threshOutput};
170+
// int ch[] = {c, 0};
171+
// cv::mixChannels(lBlurred, 1, lOutput, 1, ch, 1);
172+
173+
int thresholdLevel = 3;
174+
175+
int t = 60;
176+
int l =2;
177+
// for (int l = thresholdLevel-1 ; l >= 0; l--) {
178+
// for (int l = 0; l < thresholdLevel; l++) {
179+
// if (l == 0) {
180+
// t = 60;
181+
// while (t >= 10) {
182+
// cv::Canny(threshOutput, edged, t, t * 2);
183+
// cv::dilate(edged, edged, cv::Mat(), cv::Point(-1, -1), 2);
184+
// findSquares(
185+
// edged,
186+
// width,
187+
// height,
188+
// foundSquares);
189+
// if (foundSquares.size() > 0) {
190+
// break;
191+
// }
192+
// Call findCannySquares here with appropriate parameters
193+
// t -= 10;
194+
// }
195+
// } else {
196+
// cv::threshold(blurred, edged, (200 - 175 / (l + 2.0)), 256.0,
197+
// cv::THRESH_BINARY);
198+
findSquares(image, width, height, foundSquares);
199+
// Call findThreshSquares here with appropriate parameters
200+
// }
201+
202+
// if (foundSquares.size() > 0) {
203+
// stop as soon as find some
204+
// break;
205+
// }
206+
// }
207+
// }
208+
209+
int marge = static_cast<int>(width * 0.01);
210+
std::vector<PointAndArea> squaresProba;
211+
bool probable;
212+
213+
for (size_t i = 0; i < foundSquares.size(); i++) {
214+
probable = true;
215+
std::vector<cv::Point> pointsProba = foundSquares[i].first;
216+
for (const cv::Point &p: pointsProba) {
217+
if (p.x < marge || p.x >= width - marge || p.y < marge || p.y >= height - marge) {
218+
probable = false;
219+
break;
220+
}
221+
}
222+
if (probable) {
223+
squaresProba.push_back(foundSquares[i]);
224+
}
225+
}
226+
227+
int largestContourIndex = 0;
228+
229+
if (!squaresProba.empty()) {
230+
double largestArea = -1.0;
231+
for (size_t i = 0; i < squaresProba.size(); i++) {
232+
double a = squaresProba[i].second;
233+
if (a > largestArea && a < width * height) {
234+
largestArea = a;
235+
largestContourIndex = static_cast<int>(i);
236+
}
237+
}
238+
squares.push_back(squaresProba[largestContourIndex]);
239+
}
240+
241+
for (size_t id = 0; id < squaresProba.size(); id++) {
242+
if (static_cast<int>(id) != largestContourIndex) {
243+
squares.push_back(squaresProba[id]);
244+
}
245+
}
246+
blurred.release();
247+
image.release();
248+
if (squares.size() > 0) {
249+
sort(squares.begin(), squares.end(), sortByArea);
250+
std::vector< std::vector<Point>> result;
251+
for (int i = 0; i < squares.size(); i++) {
252+
std::vector<Point> points = squares[i].first;
253+
for (int j = 0; j < points.size(); j++) {
254+
points[j] *= resizeScale;
255+
}
256+
result.push_back(sortPointClockwise(points));
257+
}
258+
return result;
259+
260+
}
261+
return vector<vector<Point>>();
262+
}
263+
264+
Mat DocumentDetector::resizeImage() {
265+
int width = image.cols;
266+
int height = image.rows;
267+
int minSize = min(width, height);
268+
if (minSize > resizeThreshold) {
269+
resizeScale = 1.0f * minSize / resizeThreshold;
270+
width = static_cast<int>(width / resizeScale);
271+
height = static_cast<int>(height / resizeScale);
272+
Size size(width, height);
273+
Mat resizedBitmap(size, CV_8UC3);
274+
resize(image, resizedBitmap, size);
275+
return resizedBitmap;
276+
}
277+
// we clone to ensure we can clean things correctly
278+
return image.clone();
279+
}
241 Bytes
Binary file not shown.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
#import "NSCropView.h"
2+
#import "OpencvDocumentProcessDelegate.h"
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#import <UIKit/UIKit.h>
2+
//#import <opencv2/opencv.hpp>
3+
4+
@interface NSCropView : UIView
5+
@property (strong, nonatomic, retain) NSArray* quads;
6+
@property (strong, nonatomic, retain) UIImage* image;
7+
@property (nonatomic, assign) CGSize imageSize;
8+
@end
9+
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
#import "NSCropView.h"
2+
3+
@implementation NSCropView
4+
5+
- (instancetype)initWithFrame:(CGRect)frame
6+
{
7+
self = [super initWithFrame:frame];
8+
self.backgroundColor = UIColor.clearColor;
9+
self.opaque = NO;
10+
return self;
11+
}
12+
13+
// Only override drawRect: if you perform custom drawing.
14+
// An empty implementation adversely affects performance during animation.
15+
- (void)drawRect:(CGRect)rect {
16+
17+
if (self.quads && [self.quads count] > 0) {
18+
CGContextRef context = UIGraphicsGetCurrentContext();
19+
CGFloat imageRatio = self.imageSize.width / self.imageSize.height;
20+
CGFloat viewRatio = self.bounds.size.width / self.bounds.size.height;
21+
CGFloat ratio;
22+
CGFloat deltaX = 0;
23+
CGFloat deltaY = 0;
24+
25+
CGFloat addedDeltaY = 35;
26+
if (imageRatio < viewRatio) {
27+
ratio = self.bounds.size.width /self.imageSize.width;
28+
deltaY += (self.bounds.size.height - self.imageSize.height * ratio) / 2;
29+
} else {
30+
ratio = self.bounds.size.height/self.imageSize.height ;
31+
deltaX += (self.bounds.size.width - self.imageSize.width * ratio) / 2;
32+
}
33+
// if (self.image) {
34+
// [self.image drawInRect:CGRectMake(deltaX, deltaY - addedDeltaY, self.bounds.size.width - 2*deltaX , self.bounds.size.height - 2* deltaY) blendMode:kCGBlendModeNormal alpha:0.5];
35+
//
36+
// }
37+
CGContextTranslateCTM(context, deltaX, deltaY - addedDeltaY);
38+
CGContextSetStrokeColorWithColor(context, [UIColor blueColor].CGColor);
39+
CGContextSetLineWidth(context, 2.0f);
40+
[self.quads enumerateObjectsUsingBlock:^(NSArray* _Nonnull quad, NSUInteger idx, BOOL * _Nonnull stop) {
41+
CGPoint startPoint = [((NSValue*)[quad objectAtIndex:0]) CGPointValue];
42+
CGContextMoveToPoint(context, startPoint.x * ratio, startPoint.y * ratio); //start at this point
43+
[quad enumerateObjectsUsingBlock:^(NSValue* _Nonnull value, NSUInteger idx, BOOL * _Nonnull stop) {
44+
CGPoint point = [value CGPointValue];
45+
CGContextAddLineToPoint(context, point.x * ratio, point.y * ratio); //start at this point
46+
}];
47+
CGContextAddLineToPoint(context, startPoint.x * ratio, startPoint.y * ratio); //start at this point
48+
}];
49+
CGContextStrokePath(context);
50+
}
51+
}
52+
53+
54+
@end
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#import <Foundation/Foundation.h>
2+
#import <CoreMedia/CoreMedia.h>
3+
#import <CoreVideo/CoreVideo.h>
4+
#import "demosvelte-Swift.h"
5+
#import "NSCropView.h"
6+
7+
@interface OpencvDocumentProcessDelegate : NSObject <ProcessRawVideoSampleBufferDelegate>
8+
@property (strong, nonatomic) NSCropView *cropView;
9+
10+
- (instancetype)initWithCropView:(NSCropView*) view;
11+
@end

0 commit comments

Comments
 (0)