Skip to content

Commit abc9016

Browse files
committedJan 9, 2025·
docs: document detection example
1 parent 4551e50 commit abc9016

File tree

6 files changed

+209
-13
lines changed

6 files changed

+209
-13
lines changed
 

‎docs/pages/examples/_meta.json

+11-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,14 @@
11
{
22
"realtimedetection": "Real-time detection",
3-
"blur": "Blur image on separated thread"
3+
"blur": "Blur image on separated thread",
4+
"document": {
5+
"title": "Document detection ↗",
6+
"href": "https://medium.com/@lukasz.kurant/real-time-document-detection-using-the-camera-in-react-native-b0cc0af3bbd9",
7+
"newWindow": true
8+
},
9+
"passthrough": {
10+
"title": "Camera passthrough ↗",
11+
"href": "https://github.com/lukaszkurantdev/react-native-fast-opencv/blob/main/example/src/examples/CameraPassthrough.tsx",
12+
"newWindow": true
13+
}
414
}

‎example/ios/Podfile.lock

+12-12
Original file line numberDiff line numberDiff line change
@@ -981,7 +981,7 @@ PODS:
981981
- Yoga
982982
- react-native-safe-area-context (4.14.0):
983983
- React-Core
984-
- react-native-skia (1.5.5):
984+
- react-native-skia (1.8.2):
985985
- DoubleConversion
986986
- glog
987987
- hermes-engine
@@ -1364,7 +1364,7 @@ PODS:
13641364
- ReactCommon/turbomodule/core
13651365
- Yoga
13661366
- SocketRocket (0.7.0)
1367-
- vision-camera-resize-plugin (3.1.0):
1367+
- vision-camera-resize-plugin (3.2.0):
13681368
- DoubleConversion
13691369
- glog
13701370
- hermes-engine
@@ -1386,16 +1386,16 @@ PODS:
13861386
- ReactCommon/turbomodule/core
13871387
- VisionCamera
13881388
- Yoga
1389-
- VisionCamera (4.6.1):
1390-
- VisionCamera/Core (= 4.6.1)
1391-
- VisionCamera/FrameProcessors (= 4.6.1)
1392-
- VisionCamera/React (= 4.6.1)
1393-
- VisionCamera/Core (4.6.1)
1394-
- VisionCamera/FrameProcessors (4.6.1):
1389+
- VisionCamera (4.6.3):
1390+
- VisionCamera/Core (= 4.6.3)
1391+
- VisionCamera/FrameProcessors (= 4.6.3)
1392+
- VisionCamera/React (= 4.6.3)
1393+
- VisionCamera/Core (4.6.3)
1394+
- VisionCamera/FrameProcessors (4.6.3):
13951395
- React
13961396
- React-callinvoker
13971397
- react-native-worklets-core
1398-
- VisionCamera/React (4.6.1):
1398+
- VisionCamera/React (4.6.3):
13991399
- React-Core
14001400
- VisionCamera/FrameProcessors
14011401
- Yoga (0.0.0)
@@ -1636,7 +1636,7 @@ SPEC CHECKSUMS:
16361636
react-native-fast-opencv: 35b0442a0b585bc919de50a28344e5454c024b13
16371637
react-native-image-picker: c3afe5472ef870d98a4b28415fc0b928161ee5f7
16381638
react-native-safe-area-context: 4532f1a0c5d34a46b9324ccaaedcb5582a302b7d
1639-
react-native-skia: e2b30a7cca22f5daef0ed243d18de58403e7a997
1639+
react-native-skia: 2bae63532997971033b297348f4156d6a012cbef
16401640
react-native-worklets-core: e0a05ed7887519277942efc866fd2785a24c86db
16411641
React-nativeconfig: 2be4363c2c4ac2b42419577774e83e4e4fd2af9f
16421642
React-NativeModulesApple: 453ada38f826a508e48872c7a7877c431af48bba
@@ -1664,8 +1664,8 @@ SPEC CHECKSUMS:
16641664
RNReanimated: af4e059a8fd0fb7a9cdf5ad35ead4699598a9447
16651665
RNScreens: 6b641f232990a9d505a6d139fd18c3c759c9d290
16661666
SocketRocket: abac6f5de4d4d62d24e11868d7a2f427e0ef940d
1667-
vision-camera-resize-plugin: 4306d5df9bce0e603bbe6ab04337f21a606f4ad1
1668-
VisionCamera: ec141897a88c2e95e8b83cf97b8e4db801e02fd6
1667+
vision-camera-resize-plugin: 8847ccd1940a61e84e23a3148bc1f1a20cecfd5f
1668+
VisionCamera: 88df4dae7196c93ecd331f105f0e5d7d95702cb3
16691669
Yoga: 6259f968a4fdf516d76a4432dead624d71aa0fb1
16701670

16711671
PODFILE CHECKSUM: ded8a41f26047703e900afe99b8a72ca375b02ca

‎example/src/App.tsx

+5
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { NavigationContainer } from '@react-navigation/native';
55
import { createNativeStackNavigator } from '@react-navigation/native-stack';
66
import { Home } from './home/Home';
77
import { CameraPassthrough } from './examples/CameraPassthrough';
8+
import { DocumentDetection } from './examples/DocumentDetection';
89

910
const Stack = createNativeStackNavigator<StackParamList>();
1011

@@ -26,6 +27,10 @@ export default function App() {
2627
name={Route.CameraRealtimeDetection}
2728
component={CameraRealtimeDetection}
2829
/>
30+
<Stack.Screen
31+
name={Route.DocumentDetection}
32+
component={DocumentDetection}
33+
/>
2934
</Stack.Navigator>
3035
</NavigationContainer>
3136
);
+173
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
import type { SkPoint } from '@shopify/react-native-skia';
2+
import { PaintStyle, PointMode, Skia, vec } from '@shopify/react-native-skia';
3+
import { useEffect } from 'react';
4+
import { StyleSheet, Text } from 'react-native';
5+
import type { PointVector } from 'react-native-fast-opencv';
6+
import {
7+
ColorConversionCodes,
8+
ContourApproximationModes,
9+
MorphShapes,
10+
MorphTypes,
11+
ObjectType,
12+
OpenCV,
13+
RetrievalModes,
14+
} from 'react-native-fast-opencv';
15+
import {
16+
Camera,
17+
useCameraDevice,
18+
useCameraPermission,
19+
useSkiaFrameProcessor,
20+
} from 'react-native-vision-camera';
21+
import { useResizePlugin } from 'vision-camera-resize-plugin';
22+
23+
const paint = Skia.Paint();
24+
const border = Skia.Paint();
25+
26+
paint.setStyle(PaintStyle.Fill);
27+
paint.setColor(Skia.Color(0x66_e7_a6_49));
28+
border.setStyle(PaintStyle.Fill);
29+
border.setColor(Skia.Color(0xff_e7_a6_49));
30+
border.setStrokeWidth(4);
31+
32+
type Point = { x: number; y: number };
33+
34+
export function DocumentDetection() {
35+
const device = useCameraDevice('back');
36+
const { hasPermission, requestPermission } = useCameraPermission();
37+
38+
const { resize } = useResizePlugin();
39+
40+
useEffect(() => {
41+
requestPermission();
42+
}, [requestPermission]);
43+
44+
const frameProcessor = useSkiaFrameProcessor((frame) => {
45+
'worklet';
46+
47+
const ratio = 500 / frame.width;
48+
const height = frame.height * ratio;
49+
const width = frame.width * ratio;
50+
51+
const resized = resize(frame, {
52+
dataType: 'uint8',
53+
pixelFormat: 'bgr',
54+
scale: {
55+
height: height,
56+
width: width,
57+
},
58+
});
59+
60+
const source = OpenCV.frameBufferToMat(height, width, 3, resized);
61+
62+
OpenCV.invoke(
63+
'cvtColor',
64+
source,
65+
source,
66+
ColorConversionCodes.COLOR_BGR2GRAY
67+
);
68+
69+
const kernel = OpenCV.createObject(ObjectType.Size, 4, 4);
70+
const blurKernel = OpenCV.createObject(ObjectType.Size, 7, 7);
71+
const structuringElement = OpenCV.invoke(
72+
'getStructuringElement',
73+
MorphShapes.MORPH_ELLIPSE,
74+
kernel
75+
);
76+
77+
OpenCV.invoke(
78+
'morphologyEx',
79+
source,
80+
source,
81+
MorphTypes.MORPH_OPEN,
82+
structuringElement
83+
);
84+
OpenCV.invoke(
85+
'morphologyEx',
86+
source,
87+
source,
88+
MorphTypes.MORPH_CLOSE,
89+
structuringElement
90+
);
91+
OpenCV.invoke('GaussianBlur', source, source, blurKernel, 0);
92+
OpenCV.invoke('Canny', source, source, 75, 100);
93+
94+
const contours = OpenCV.createObject(ObjectType.PointVectorOfVectors);
95+
96+
OpenCV.invoke(
97+
'findContours',
98+
source,
99+
contours,
100+
RetrievalModes.RETR_LIST,
101+
ContourApproximationModes.CHAIN_APPROX_SIMPLE
102+
);
103+
104+
const contoursMats = OpenCV.toJSValue(contours);
105+
106+
let greatestPolygon: PointVector | undefined;
107+
let greatestArea = 0;
108+
109+
for (let index = 0; index < contoursMats.array.length; index++) {
110+
const contour = OpenCV.copyObjectFromVector(contours, index);
111+
const { value: area } = OpenCV.invoke('contourArea', contour, false);
112+
113+
if (area > 2000 && area > greatestArea) {
114+
const peri = OpenCV.invoke('arcLength', contour, true);
115+
const approx = OpenCV.createObject(ObjectType.PointVector);
116+
117+
OpenCV.invoke('approxPolyDP', contour, approx, 0.1 * peri.value, true);
118+
119+
greatestPolygon = approx;
120+
greatestArea = area;
121+
}
122+
}
123+
124+
frame.render();
125+
126+
if (greatestPolygon) {
127+
const points: Point[] = OpenCV.toJSValue(greatestPolygon).array;
128+
129+
if (points.length === 4) {
130+
const path = Skia.Path.Make();
131+
const pointsToShow: SkPoint[] = [];
132+
133+
const lastPointX = (points[3]?.x || 0) / ratio;
134+
const lastPointY = (points[3]?.y || 0) / ratio;
135+
136+
path.moveTo(lastPointX, lastPointY);
137+
pointsToShow.push(vec(lastPointX, lastPointY));
138+
139+
for (let index = 0; index < 4; index++) {
140+
const pointX = (points[index]?.x || 0) / ratio;
141+
const pointY = (points[index]?.y || 0) / ratio;
142+
143+
path.lineTo(pointX, pointY);
144+
pointsToShow.push(vec(pointX, pointY));
145+
}
146+
147+
path.close();
148+
149+
frame.drawPath(path, paint);
150+
frame.drawPoints(PointMode.Polygon, pointsToShow, border);
151+
}
152+
}
153+
154+
OpenCV.clearBuffers();
155+
}, []);
156+
157+
if (!hasPermission) {
158+
return <Text>No permission</Text>;
159+
}
160+
161+
if (device == null) {
162+
return <Text>No device</Text>;
163+
}
164+
165+
return (
166+
<Camera
167+
style={StyleSheet.absoluteFill}
168+
device={device}
169+
isActive={true}
170+
frameProcessor={frameProcessor}
171+
/>
172+
);
173+
}

‎example/src/home/Home.tsx

+6
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,12 @@ const items: ItemDetails[] = [
2929
description: 'Detect objects in realtime',
3030
route: Route.CameraRealtimeDetection,
3131
},
32+
{
33+
title: 'Document Detection',
34+
emoji: '📷',
35+
description: 'Detect documents in realtime',
36+
route: Route.DocumentDetection,
37+
},
3238
];
3339

3440
export const Home = () => {

‎example/src/types.ts

+2
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,15 @@ export enum Route {
55
ImageBlur = 'ImageBlur',
66
CameraPassthrough = 'CameraPassthrough',
77
CameraRealtimeDetection = 'CameraRealtimeDetection',
8+
DocumentDetection = 'DocumentDetection',
89
}
910

1011
export interface StackParamList extends ParamListBase {
1112
Home: undefined;
1213
ImageBlur: undefined;
1314
CameraPassthrough: undefined;
1415
CameraRealtimeDetection: undefined;
16+
DocumentDetection: undefined;
1517
}
1618

1719
// https://reactnavigation.org/docs/typescript#specifying-default-types-for-usenavigation-link-ref-etc

0 commit comments

Comments
 (0)
Please sign in to comment.