Developing an Object Measurement System from Images or Video
This is a task we solve using computer vision. On a production line, a part just came out of the machine — hot (80°C). Contact measurement with a caliper deforms the surface, introducing an error of up to 0.5 mm. Non-contact measurement via camera: a single shot in 50 ms, accuracy ±0.1 mm, data straight to SCADA. No risk of burns or conveyor stoppage.
Problems We Solve
Quality control on the production line. The part just came out of the machine — deviations from the drawing are detected instantly. The system measures length, width, diameter, angles, and contour area in 50 ms. Out-of-tolerance triggers an alarm or line stop.
Field measurements without access to the object. Need to know the width of a crack in a wall or the diameter of a pipe at height? Place an ArUco marker next to it, take a photo — in a second the dimensions are known. Ladders and rulers are unnecessary.
Logistics and warehouse. Box dimensions on a conveyor belt are measured in motion. The system automatically assigns length, width, and height to each unit — optimizing container loading.
How to Ensure Millimetre-Accurate Measurements
The key parameter is scale. We use three methods:
-
Calibrated camera — photograph a calibration plate once from the working distance, compute pixels_per_mm. Accuracy ±0.1 mm. Ideal for static conveyors.
-
ArUco marker in the frame — a marker of known size (e.g., 50 mm) is placed next to the object. The system detects the marker, computes the scale, then measures the object. Accuracy ±1–5 mm, suitable for field conditions.
-
Stereo pair — two cameras spaced 10 cm apart produce a 3D point cloud. From this we extract overall dimensions, depth, volume. Accuracy ±0.5–2 mm on objects up to 1 m.
The choice of method depends on the task. For a conveyor with a fixed camera position — calibration. For a mobile tablet — ArUco. For large objects (furniture, pallets) — stereo or LiDAR+RGB.
How to Choose the Measurement Method?
-
If the object moves on a conveyor at constant speed and the camera is fixed — choose a calibrated camera. Maximum accuracy ±0.1 mm and measurement time 50 ms.
-
If you are in a warehouse or field — use an ArUco marker. Print a 5×5 cm marker, glue it onto a rigid base, and place it next to the object. The system will automatically determine the scale.
-
If you need to measure volume or depth — use a stereo pair. Two cameras with a 10 cm baseline build a 3D model and output dimensions in millimetres.
Step-by-Step: How the System Measures an Object
- Image capture. The camera takes a snapshot (or frame from video). Resolution and frame rate are configured.
- Scale determination. The system finds the calibration object or ArUco marker, computes pixels_per_mm.
- Object segmentation. We use thresholding (Otsu) or a neural network for complex contours.
- Dimension extraction. From the contour we compute bounding box, minimal rectangle, perimeter, area.
- Conversion to millimetres. Multiply pixel values by pixels_per_mm.
- Result output. Send data as JSON, to a database, or to the operator's screen.
What the Work Includes
- Task analysis: Send us a photo/video of the object with shooting conditions (distance, lighting, motion). We choose the method and prepare an estimate in 1 day.
- Prototype: In 3–5 days we build a demo version for your scenario. You test it on real data.
- Development: We implement segmentation, calibration, and measurement algorithms. Stack: OpenCV, PyTorch (if a neural network for complex contours is needed), Docker, FastAPI.
- Integration: Embed the module into your PLC/SCADA, add REST API, MQTT, Modbus.
- Documentation and training: Deliver code, API description, operator instructions.
- Support: 3 months free support, then by contract.
Timeframes (Approximate)
| Task | Duration |
|---|---|
| 2D measurement on a conveyor | 2–4 weeks |
| System with ArUco for field use | 3–5 weeks |
| 3D stereo system | 6–10 weeks |
Cost is calculated individually and depends on segmentation complexity, number of cameras, and need for neural networks. Write to us — we will give a preliminary estimate in 1 day.
Why Choose Us?
Over 10 years of experience in industrial CV. We have implemented 50+ projects for mechanical engineering, logistics, and medicine. Accuracy guarantee: we record metrological characteristics in the contract. We always provide a validation report. Development processes are standardized according to ISO 9001. Compared to Western Halcon/Matrox solutions, our solution is 2–3 times cheaper with comparable accuracy, and implementation time is 1.5 times shorter.
Our customers save up to 2 million rubles per year on quality control. For example, one client reduced inspection time by 80%, equivalent to savings of about 1.5 million rubles per year. Get a consultation from an engineer with 10+ years of CV experience — contact us.
Scaling Methods
Method 1: Calibrated Camera
Once we photograph an object of known size (calibration plate) from the working distance, compute the pixels_per_mm coefficient:
import cv2
import numpy as np
class CalibratedMeasurement:
def __init__(self, pixels_per_mm: float,
camera_matrix: np.ndarray = None,
dist_coefficients: np.ndarray = None):
self.ppm = pixels_per_mm
self.camera_matrix = camera_matrix
self.dist_coefficients = dist_coefficients
def calibrate_from_reference(self, image: np.ndarray,
known_width_mm: float) -> float:
"""Calibration using an object of known width"""
# Assume object is already aligned and occupies ~80% of width
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
_, binary = cv2.threshold(gray, 0, 255,
cv2.THRESH_BINARY + cv2.THRESH_OTSU)
contours, _ = cv2.findContours(binary, cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)
main = max(contours, key=cv2.contourArea)
x, y, w, h = cv2.boundingRect(main)
self.ppm = w / known_width_mm
return self.ppm
def measure_contour(self, image: np.ndarray) -> dict:
"""Measure object's key parameters"""
if self.camera_matrix is not None:
# Correct lens distortion
image = cv2.undistort(image, self.camera_matrix,
self.dist_coefficients)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
_, binary = cv2.threshold(gray, 0, 255,
cv2.THRESH_BINARY + cv2.THRESH_OTSU)
contours, _ = cv2.findContours(binary, cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)
if not contours:
return {'measured': False}
c = max(contours, key=cv2.contourArea)
# Bounding rectangle
x, y, w, h = cv2.boundingRect(c)
# Minimal rotated rectangle
rect = cv2.minAreaRect(c)
(cx, cy), (rw, rh), angle = rect
min_side = min(rw, rh)
max_side = max(rw, rh)
# Perimeter via arc length
perimeter_px = cv2.arcLength(c, True)
# Area
area_px = cv2.contourArea(c)
return {
'width_mm': round(w / self.ppm, 3),
'height_mm': round(h / self.ppm, 3),
'length_mm': round(max_side / self.ppm, 3),
'width_min_mm': round(min_side / self.ppm, 3),
'angle_deg': round(angle, 2),
'perimeter_mm': round(perimeter_px / self.ppm, 3),
'area_mm2': round(area_px / self.ppm**2, 3),
'center': (round(cx / self.ppm, 2), round(cy / self.ppm, 2))
}
Method 2: Reference Object in Frame (ArUco Markers)
import cv2.aruco as aruco
def measure_with_aruco(image: np.ndarray,
marker_size_mm: float = 50.0) -> dict:
"""Measurement using ArUco marker as reference"""
aruco_dict = aruco.getPredefinedDictionary(aruco.DICT_4X4_250)
detector = aruco.ArucoDetector(aruco_dict)
corners, ids, _ = detector.detectMarkers(image)
if ids is None:
return {'error': 'no_aruco_marker_found'}
# Compute pixels_per_mm from the marker
marker_corners = corners[0][0]
marker_width_px = np.linalg.norm(marker_corners[0] - marker_corners[1])
ppm = marker_width_px / marker_size_mm
# Then standard measurement
measurer = CalibratedMeasurement(pixels_per_mm=ppm)
return measurer.measure_contour(image)
3D Measurement via Stereo Pair
class StereoCameraMeasurement:
def __init__(self, stereo_calibration: dict):
self.Q = stereo_calibration['Q'] # disparity-to-depth matrix
self.baseline_mm = stereo_calibration['baseline_mm']
self.focal_length_px = stereo_calibration['focal_length_px']
def measure_3d(self, left_img: np.ndarray,
right_img: np.ndarray) -> dict:
# Stereo matching
stereo = cv2.StereoSGBM_create(
minDisparity=0,
numDisparities=96,
blockSize=11,
P1=8 * 3 * 11**2,
P2=32 * 3 * 11**2,
disp12MaxDiff=1,
uniquenessRatio=10,
speckleWindowSize=100,
speckleRange=32
)
disparity = stereo.compute(
cv2.cvtColor(left_img, cv2.COLOR_BGR2GRAY),
cv2.cvtColor(right_img, cv2.COLOR_BGR2GRAY)
).astype(np.float32) / 16.0
# Convert disparity to 3D point cloud
points_3d = cv2.reprojectImageTo3D(disparity, self.Q)
return self._extract_dimensions_3d(points_3d)
Accuracy and Applications
| Method | Range | Accuracy | Application |
|---|---|---|---|
| 2D with calibration (fixed distance) | 1–500 mm | ±0.1–0.5 mm | Conveyor, QC |
| ArUco reference | 10–2000 mm | ±1–5 mm | Field measurements |
| Stereo (10 cm baseline) | 50–1000 mm | ±0.5–2 mm | 3D measurement |
| LiDAR + RGB | 100–5000 mm | ±1–3 mm | Large objects |
According to a NIST study, non-contact measurement reduces error by 40%. Ready to discuss your task. Write to us — we will evaluate your project in 1 day. Get a consultation from an engineer.







