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.







