1# Copyright (c) 2025 NXP
2#
3# SPDX-License-Identifier: Apache-2.0
4
5import cv2
6import numpy as np
7
8
9class UVCCamera:
10    def __init__(self, config):
11        self.device_id = config.get("device_id", 0)
12        self.res_x = config.get("res_x", 1280)
13        self.res_y = config.get("res_y", 720)
14        self.cap = cv2.VideoCapture(self.device_id)
15        self.prev_frame = None
16        self.current_alarms = 0
17        self.alarm_duration = 5  # Alarm duration in frames
18        self.fingerprint_cache = {}  # Store video fingerprints
19        self._original_wb = 0
20
21    def initialize(self):
22        if not self.cap.isOpened():
23            raise Exception(f"Failed to open camera {self.device_id}")
24        self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, self.res_x)  # Set resolution
25        self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, self.res_y)
26        self.cap.set(cv2.CAP_PROP_AUTOFOCUS, 1)
27        # Read initial 10 frames to stabilize camera
28        for _ in range(10):
29            self.get_frame()
30
31        # Use first 120 frames to optimize focus
32        best_focus_score = 0
33        for i in range(120):
34            ret, frame = self.get_frame()
35            if ret:
36                current_score = self.smart_focus(frame)
37                if current_score > best_focus_score:
38                    best_focus_score = current_score
39                if i % 20 == 0:  # Periodically adjust focus
40                    self.auto_focus(current_score < 0.5)
41        self.auto_white_balance(True)
42        self.print_settings()
43
44    def get_frame(self):
45        ret, frame = self.cap.read()
46        if ret:
47            frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
48        return ret, frame
49
50    def show_frame(self, frame):
51        cv2.imshow(f"Camera Preview (Device {self.device_id})", frame)
52        cv2.waitKey(1)
53
54    def auto_focus(self, enable: bool):
55        """Auto focus control
56
57        Args:
58            enable: True to enable auto focus, False to disable
59        """
60        self.cap.set(cv2.CAP_PROP_AUTOFOCUS, 1 if enable else 0)
61
62    def smart_focus(self, frame):
63        """Smart focus algorithm based on image sharpness
64
65        Args:
66            frame: Current video frame
67
68        Returns:
69            Focus score value (0-1)
70        """
71        if frame is None:
72            return 0
73
74        # Convert to grayscale
75        gray = cv2.cvtColor(frame, cv2.COLOR_RGB2GRAY)
76
77        # Calculate Laplacian variance as sharpness metric
78        laplacian = cv2.Laplacian(gray, cv2.CV_64F)
79        focus_score = laplacian.var() / 1000  # Normalized to 0-1 range
80
81        # Adjust focus based on sharpness
82        if focus_score < 0.3:  # Image is blurry
83            self.auto_focus(True)
84        elif focus_score > 0.7:  # Image is sharp
85            self.auto_focus(False)
86
87        return min(max(focus_score, 0), 1)  # Ensure value is within 0-1 range
88
89    def auto_white_balance(self, enable):
90        """Enable/disable auto white balance with algorithm optimization"""
91        if enable:
92            self._original_wb = self.cap.get(cv2.CAP_PROP_WB_TEMPERATURE)
93
94            self.cap.set(cv2.CAP_PROP_AUTO_WB, 1)
95
96            # Sample frames and analyze white balance
97            wb_scores = []
98            for _ in range(10):  # Sample 10 frames for better accuracy
99                ret, frame = self.cap.read()
100                if not ret:
101                    break
102
103                # Convert to LAB color space and analyze A/B channels
104                lab = cv2.cvtColor(frame, cv2.COLOR_BGR2LAB)
105                a_channel, b_channel = lab[:, :, 1], lab[:, :, 2]
106
107                # Calculate white balance score (lower is better)
108                wb_score = np.std(a_channel) + np.std(b_channel)
109                wb_scores.append(wb_score)
110
111                # If score improves significantly, keep current WB
112                if len(wb_scores) > 1 and wb_score < min(wb_scores[:-1]) * 0.9:
113                    break
114        else:
115            if hasattr(self, "_original_wb"):
116                self.cap.set(cv2.CAP_PROP_AUTO_WB, 0)
117                self.cap.set(cv2.CAP_PROP_WB_TEMPERATURE, self._original_wb)
118                ret, frame = self.cap.read()
119                if ret:
120                    cv2.waitKey(1)
121
122    def capture_image(self, save_path=None):
123        """Capture current frame at highest resolution and optionally save to file"""
124        # Set to maximum resolution
125        self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, self.cap.get(cv2.CAP_PROP_FRAME_WIDTH))
126        self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, self.cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
127
128        ret, frame = self.cap.read()
129        if not ret:
130            return None
131
132        if save_path:
133            cv2.imwrite(save_path, frame)
134        return frame
135
136    def analyze_image(self, image):
137        """Perform basic image analysis on captured frame"""
138        if image is None:
139            return None
140
141        analysis = {}
142        # Brightness analysis
143        gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
144        analysis["brightness"] = np.mean(gray)
145
146        # Color analysis
147        lab = cv2.cvtColor(image, cv2.COLOR_BGR2LAB)
148        analysis["color_balance"] = {
149            "a_channel": np.mean(lab[:, :, 1]),
150            "b_channel": np.mean(lab[:, :, 2]),
151        }
152
153        # Sharpness analysis
154        analysis["sharpness"] = cv2.Laplacian(gray, cv2.CV_64F).var()
155
156        return analysis
157
158    def show_analysis(self, analysis, frame=None):
159        """Display image analysis results on frame if provided"""
160        if analysis is None:
161            print("No analysis results to display")
162            return
163
164        # Print analysis results
165        print("Image Analysis Results:")
166        print(f"  Brightness: {analysis['brightness']:.2f}")
167        print(f"  Color Balance - A Channel: {analysis['color_balance']['a_channel']:.2f}")
168        print(f"  Color Balance - B Channel: {analysis['color_balance']['b_channel']:.2f}")
169        print(f"  Sharpness: {analysis['sharpness']:.2f}")
170
171        # 在帧上显示分析结果
172        if frame is not None:
173            text_y = 30
174            cv2.putText(
175                frame,
176                f"Brightness: {analysis['brightness']:.2f}",
177                (10, text_y),
178                cv2.FONT_HERSHEY_SIMPLEX,
179                0.7,
180                (0, 255, 0),
181                2,
182            )
183            text_y += 30
184            cv2.putText(
185                frame,
186                f"A Channel: {analysis['color_balance']['a_channel']:.2f}",
187                (10, text_y),
188                cv2.FONT_HERSHEY_SIMPLEX,
189                0.7,
190                (0, 255, 0),
191                2,
192            )
193            text_y += 30
194            cv2.putText(
195                frame,
196                f"B Channel: {analysis['color_balance']['b_channel']:.2f}",
197                (10, text_y),
198                cv2.FONT_HERSHEY_SIMPLEX,
199                0.7,
200                (0, 255, 0),
201                2,
202            )
203            text_y += 30
204            cv2.putText(
205                frame,
206                f"Sharpness: {analysis['sharpness']:.2f}",
207                (10, text_y),
208                cv2.FONT_HERSHEY_SIMPLEX,
209                0.7,
210                (0, 255, 0),
211                2,
212            )
213
214    def load_image(self, file_path):
215        """Load a saved image from file
216
217        Args:
218            file_path: Path to the image file
219
220        Returns:
221            The loaded image in BGR format, or None if loading fails
222        """
223        try:
224            image = cv2.imread(file_path)
225            if image is None:
226                print(f"Failed to load image from {file_path}")
227            return image
228        except Exception as e:
229            print(f"Error loading image: {str(e)}")
230            return None
231
232    def analyze_multiple_frames(self, num_frames=10):
233        """Analyze multiple frames and return average results"""
234        results = []
235
236        for _ in range(num_frames):
237            frame = self.capture_image()
238            if frame is None:
239                continue
240
241            analysis = self.analyze_image(frame)
242            results.append(analysis)
243
244        if not results:
245            return None
246
247        # Calculate averages
248        avg_analysis = {
249            "brightness": np.mean([r["brightness"] for r in results]),
250            "color_balance": {
251                "a_channel": np.mean([r["color_balance"]["a_channel"] for r in results]),
252                "b_channel": np.mean([r["color_balance"]["b_channel"] for r in results]),
253            },
254            "sharpness": np.mean([r["sharpness"] for r in results]),
255        }
256
257        return avg_analysis
258
259    def print_settings(self):
260        """Print current camera settings"""
261        print(f"Camera {self.device_id} Settings:")
262        print(
263            f"Resolution: {int(self.cap.get(cv2.CAP_PROP_FRAME_WIDTH))}\
264                x{int(self.cap.get(cv2.CAP_PROP_FRAME_HEIGHT))}"
265        )
266        print(f"  FPS: {self.cap.get(cv2.CAP_PROP_FPS):.2f}")
267        print(f"  Auto Focus: {'ON' if self.cap.get(cv2.CAP_PROP_AUTOFOCUS) else 'OFF'}")
268        print(f"  Auto White Balance: {'ON' if self.cap.get(cv2.CAP_PROP_AUTO_WB) else 'OFF'}")
269
270    def release(self):
271        self.cap.release()
272        cv2.destroyAllWindows()
273