Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,4 @@ stats.json
# dev environments
.vscode/*
**/.DS_Store
test_images/*.jpeg
1 change: 1 addition & 0 deletions .npmignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
src/
test_images/
yarn.lock

tsconfig.json
Expand Down
106 changes: 105 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,8 @@ Note that this example produces a raw H264 video. Wrapping it in a video contain
- [`SensorMode`](#sensormode)
- [`ExposureMode`](#exposuremode)
- [`AwbMode`](#awbmode)
- [`DynamicRange`](#dynamicRange)
- [`ImageEffect`](#imageEffect)

## `StillCamera`

Expand Down Expand Up @@ -261,8 +263,21 @@ const stillCamera = new StillCamera({
- `exposureCompensation: number` - _Range: `-10`-`10`; Default: `0`_
- [`exposureMode: ExposureMode`](#exposuremode) - _Default: `ExposureMode.Auto`_
- [`awbMode: AwbMode`](#awbmode) - _Default: `AwbMode.Auto`_
- `awbGains: [number, number]` - _Default: `null`_
- `analogGain: number` - _Default: `0`_
- `digitalGain: number` - _Default: `0`_
- `quality: number` - _Default: `100`_
- `colourEffect: [number, number]` - _Default: `[0,0]`_
- [`imageEffect: ImxfxMode`](#imageeffect) - _Default: `ImxfxMode.None`_
- [`dynamicRange: DynamicRange`](#dynamicrange) - _Default: `DynamicRange.Off`_
- `videoStabilisation: boolean` - _Default: `false`_
- `raw: boolean` - _Default: `false`_
- [`meteringMode`](#meteringMode) - _Default: `MeteringMode.Off`_
- `thumbnail: [number, number, number] | 'none'` - _Default: `[64, 48, 35]`_
- [`flickerMode`](#flickerMode) - _Default: `null`_
- `burst: boolean` - _Default: `false`_
- `roi: [number, number, number, number]` - _Default: `null`_
- `statistics: boolean` - _Default: `false`_

### `StillCamera.takeImage(): Promise<Buffer>`

Expand Down Expand Up @@ -307,8 +322,17 @@ const streamCamera = new StreamCamera({
- `exposureCompensation: number` - _Range: `-10`-`10`; Default: `0`_
- [`exposureMode: ExposureMode`](#exposuremode) - _Default: `ExposureMode.Auto`_
- [`awbMode: AwbMode`](#awbmode) - _Default: `AwbMode.Auto`_
- `awbGains: [number, number]` - _Default: `null`_
- `analogGain: number` - _Default: `0`_
- `digitalGain: number` - _Default: `0`_
- `colourEffect: [number, number]` - _Default: `[0,0]`_
- [`imageEffect: ImxfxMode`](#imageeffect) - _Default: `ImxfxMode.None`_
- [`dynamicRange: DynamicRange`](#dynamicrange) - _Default: `DynamicRange.Off`_
- `videoStabilisation: boolean` - _Default: `false`_
- [`meteringMode`](#meteringMode) - _Default: `MeteringMode.Off`_
- [`flickerMode`](#flickerMode) - _Default: `null`_
- `roi: [number, number, number, number]` - _Default: `null`_
- `statistics: boolean` - _Default: `false`_

### `startCapture(): Promise<void>`

Expand Down Expand Up @@ -446,7 +470,19 @@ These are slightly different depending on the version of Raspberry Pi camera you
| 4 | 1640x1232 | 4:3 | 0.1-40fps | Full | 2x2 |
| 5 | 1640x922 | 16:9 | 0.1-40fps | Full | 2x2 |
| 6 | 1280x720 | 16:9 | 40-90fps | Partial | 2x2 |
| 7 | 640x480 | 4:3 | 40-90fps | Partial | 2x2 |
| 7 | 640x480 | 4:3 | 40-200fps* | Partial | 2x2 |

*For frame rates over 120fps, it is necessary to turn off automatic exposure and gain control using -ex off. Doing so should achieve the higher frame rates, but exposure time and gains will need to be set to fixed values supplied by the user.

#### HQ Camera (IMX477):

| Mode | Size | Aspect Ratio | Frame rates | FOV | Binning |
|------|---------------------|--------------|-------------|---------|-------------|
| 0 | automatic selection | | | | |
| 1 | 2028x1080 | 169:90 | 0.1-50fps | Partial | 2x2 binned |
| 2 | 2028x1520 | 4:3 | 0.1-50fps | Full | 2x2 binned |
| 3 | 4056x3040 | 4:3 | 0.005-10fps | Full | None |
| 4 | 1332x990 | 74:55 | 50.1-120fps | Partial | 2x2 binned |

## `ExposureMode`

Expand Down Expand Up @@ -489,3 +525,71 @@ White balance mode options.
```javascript
import { AwbMode } from 'pi-camera-connect';
```

## `ImageEffect`

Image Effect options.

- `ImxfxMode.None`
- `ImxfxMode.Negative`
- `ImxfxMode.Solarise`
- `ImxfxMode.Sketch`
- `ImxfxMode.Denoise`
- `ImxfxMode.Emboss`
- `ImxfxMode.OilPaint`
- `ImxfxMode.Hatch`
- `ImxfxMode.GPen`
- `ImxfxMode.Pastel`
- `ImxfxMode.Watercolour`
- `ImxfxMode.Film`
- `ImxfxMode.Blur`
- `ImxfxMode.Saturation`
- `ImxfxMode.ColourSwap`
- `ImxfxMode.WashedOut`
- `ImxfxMode.Posterise`
- `ImxfxMode.ColourPoint`
- `ImxfxMode.ColourBalance`
- `ImxfxMode.Cartoon`

```javascript
import { ImxfxMode } from 'pi-camera-connect';
```

## `DynamicRange`

Dynamic Range options.

- `DynamicRange.Off`
- `DynamicRange.Low`
- `DynamicRange.Medium`
- `DynamicRange.High`

```javascript
import { DynamicRange } from 'pi-camera-connect';
```

## `MeteringMode`

Dynamic Range options.

- `MeteringMode.Average`
- `MeteringMode.Spot`
- `MeteringMode.Backlit`
- `MeteringMode.Matrix`

```javascript
import { MeteringMode } from 'pi-camera-connect';
```

## `FlickerMode`

Dynamic Range options.

- `FlickerMode.Off`
- `FlickerMode.Auto`
- `FlickerMode.50hz`
- `FlickerMode.60hz`

```javascript
import { FlickerMode } from 'pi-camera-connect';
```
44 changes: 44 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,47 @@ export enum AwbMode {
Horizon = 'horizon',
GreyWorld = 'greyworld',
}

export enum ImxfxMode {
None = 'none',
Negative = 'negative',
Solarise = 'solarise',
Sketch = 'sketch',
Denoise = 'denoise',
Emboss = 'emboss',
OilPaint = 'oilpaint',
Hatch = 'hatch',
GPen = 'gpen',
Pastel = 'pastel',
Watercolour = 'watercolour',
Film = 'film',
Blur = 'blur',
Saturation = 'saturation',
ColourSwap = 'colourswap',
WashedOut = 'washedout',
Posterise = 'posterise',
ColourPoint = 'colourpoint',
ColourBalance = 'colourbalance',
Cartoon = 'cartoon',
}

export enum DynamicRange {
Off = 'off',
Low = 'low',
Medium = 'medium',
High = 'high',
}

export enum MeteringMode {
Average = 'average',
Spot = 'spot',
Backlit = 'backlit',
Matrix = 'matrix',
}

export enum FlickerMode {
Off = 'off',
Auto = 'auto',
'50hz' = '50hz',
'60Hz' = '60hz',
}
75 changes: 73 additions & 2 deletions src/lib/shared-args.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,12 @@ export function getSharedArgs(options: StillOptions | StreamOptions): string[] {
...(options.saturation ? ['--saturation', options.saturation.toString()] : []),

/**
* ISO
* ISO (100 to 800)
*/
...(options.iso ? ['--ISO', options.iso.toString()] : []),

/**
* EV Compensation
* EV Compensation (-10 to 10; default 0)
*/
...(options.exposureCompensation ? ['--ev', options.exposureCompensation.toString()] : []),

Expand All @@ -85,14 +85,85 @@ export function getSharedArgs(options: StillOptions | StreamOptions): string[] {
*/
...(options.awbMode ? ['--awb', options.awbMode.toString()] : []),

/**
* Sets the blue and red channel gains if awbMode is Off
*/
...(options.awbGains ? ['--awbgains', options.awbGains.toString()] : []),

/**
* Analog Gain
* Sets the analog gain value directly on the sensor (floating point value from
* 1.0 to 8.0 for the OV5647 sensor on Camera Module V1, and 1.0 to 12.0 for the
* IMX219 sensor on Camera Module V2 and the IMX447 on the HQ Camera).
*/
...(options.analogGain ? ['--analoggain', options.analogGain.toString()] : []),

/**
* Digital Gain
* Sets the digital gain value applied by the ISP
* (floating point value from 1.0 to 64.0,
* but values over about 4.0 willproduce overexposed images)
*/
...(options.digitalGain ? ['--digitalgain', options.digitalGain.toString()] : []),

/**
* Image Effect
*/
...(options.imageEffect ? ['--imxfx', options.imageEffect.toString()] : []),

/**
* Dynamic Range Control
*/
...(options.dynamicRange ? ['--drc', options.dynamicRange] : []),

/**
* Colour Effects
* The supplied U and V parameters (range 0 to 255) are applied to
* the U and Y channels of the image. For example, --colfx 128:128
* should result in a monochrome image.
*/
...(options.colourEffect ? ['--colfx', options.colourEffect.join(':')] : []),

/**
* Metering
* Specify the metering mode used for the preview and capture.
*/
...(options.meteringMode ? ['--metering', options.meteringMode] : []),

/**
* Flicker Avoid Mode
* Set a mode to compensate for lights flickering at the mains frequency,
* which can be seen as a dark horizontal band across an image.
* Flicker avoidance locks the exposure time to a multiple of the mains
* flicker frequency (8.33ms for 60Hz, or 10ms for 50Hz).
* This means that images can be noisier as the control algorithm has to
* increase the gain instead of exposure time should it wish for an
* intermediate exposure value. auto can be confused by external factors,
* therefore it is preferable to leave this setting off unless actually required.
*/
...(options.flickerMode ? ['--flicker', options.flickerMode] : []),

/**
* Video Stabilisation
* In video mode only, turn on video stabilization.
*/
...(options.videoStabilisation ? ['--vstab'] : []),

/**
* Statistics
* Force recomputation of statistics on stills capture pass. Digital gain and AWB are
* recomputed based on the actual capture frame statistics,
* rather than the preceding preview frame.
*/
...(options.statistics ? ['--stats'] : []),

/**
* Sensor region of interest
* Allows the specification of the area of the sensor to be used as
* the source for the preview and capture. This is defined as x,y for
* the top left corner, and a width and height, all values in
* normalised coordinates (0.0-1.0).
*/
...(options.roi ? ['--roi', options.roi.toString()] : []),
];
}
15 changes: 15 additions & 0 deletions src/lib/still-camera.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,24 @@
import * as fs from 'fs';
import { performance } from 'perf_hooks';

import StillCamera from './still-camera';

const TEST_IMAGES_DIR = 'test_images';

if (!fs.existsSync(TEST_IMAGES_DIR)) {
fs.mkdirSync(TEST_IMAGES_DIR);
}

test('takeImage() returns JPEG', async () => {
const t0 = performance.now();

const stillCamera = new StillCamera();

const jpegImage = await stillCamera.takeImage();
const t1 = performance.now();

const time = ((t1 - t0) / 1000).toFixed(2);
await fs.promises.writeFile(`test_images/stillCapture_(${time}-secs).jpeg`, jpegImage, 'binary');

expect(jpegImage.indexOf(StillCamera.jpegSignature)).toBe(0);
});
Loading