OpenCV.js 的测试和调试有哪些策略?
OpenCV.js 的测试和调试对于确保应用质量和性能至关重要。以下是全面的测试和调试策略:1. 单元测试使用 Jest 进行单元测试// imageProcessor.test.jsdescribe('ImageProcessor', () => { let processor; beforeEach(() => { // 确保 OpenCV.js 已加载 if (typeof cv === 'undefined') { throw new Error('OpenCV.js is not loaded'); } processor = new ImageProcessor(); }); afterEach(() => { if (processor) { processor.cleanup(); } }); test('should convert image to grayscale', () => { const src = new cv.Mat(100, 100, cv.CV_8UC3); const dst = new cv.Mat(); try { processor.convertToGrayscale(src, dst); expect(dst.channels()).toBe(1); expect(dst.rows).toBe(100); expect(dst.cols).toBe(100); } finally { src.delete(); dst.delete(); } }); test('should detect edges in image', () => { const src = new cv.Mat(100, 100, cv.CV_8UC1); const dst = new cv.Mat(); try { processor.detectEdges(src, dst); expect(dst.channels()).toBe(1); expect(dst.type()).toBe(cv.CV_8UC1); } finally { src.delete(); dst.delete(); } }); test('should handle invalid input gracefully', () => { expect(() => { processor.processImage(null); }).toThrow(); });});测试工具函数class TestUtils { static createTestMat(width, height, type) { const mat = new cv.Mat(height, width, type); mat.data.fill(128); return mat; } static createTestImage(width, height) { const canvas = document.createElement('canvas'); canvas.width = width; canvas.height = height; const ctx = canvas.getContext('2d'); ctx.fillStyle = '#808080'; ctx.fillRect(0, 0, width, height); return canvas; } static compareMats(mat1, mat2, tolerance = 0) { if (mat1.rows !== mat2.rows || mat1.cols !== mat2.cols) { return false; } for (let i = 0; i < mat1.data.length; i++) { if (Math.abs(mat1.data[i] - mat2.data[i]) > tolerance) { return false; } } return true; } static measureExecutionTime(fn) { const start = performance.now(); fn(); const end = performance.now(); return end - start; }}2. 集成测试端到端图像处理测试describe('Image Processing Pipeline', () => { test('should process complete image pipeline', async () => { const inputImage = TestUtils.createTestImage(640, 480); const processor = new ImageProcessor(); try { // 加载图像 const src = cv.imread(inputImage); // 处理流程 const gray = new cv.Mat(); const blurred = new cv.Mat(); const edges = new cv.Mat(); try { cv.cvtColor(src, gray, cv.COLOR_RGBA2GRAY); cv.GaussianBlur(gray, blurred, new cv.Size(5, 5), 0); cv.Canny(blurred, edges, 50, 100); // 验证结果 expect(edges.channels()).toBe(1); expect(edges.rows).toBe(480); expect(edges.cols).toBe(640); } finally { gray.delete(); blurred.delete(); edges.delete(); } src.delete(); } finally { processor.cleanup(); } });});3. 性能测试基准测试class PerformanceBenchmark { constructor() { this.results = []; } benchmark(name, fn, iterations = 100) { const times = []; for (let i = 0; i < iterations; i++) { const start = performance.now(); fn(); const end = performance.now(); times.push(end - start); } const stats = { name, min: Math.min(...times), max: Math.max(...times), mean: times.reduce((a, b) => a + b, 0) / times.length, median: this.median(times), stdDev: this.standardDeviation(times) }; this.results.push(stats); return stats; } median(arr) { const sorted = [...arr].sort((a, b) => a - b); const mid = Math.floor(sorted.length / 2); return sorted.length % 2 !== 0 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2; } standardDeviation(arr) { const mean = arr.reduce((a, b) => a + b, 0) / arr.length; const squareDiffs = arr.map(value => Math.pow(value - mean, 2)); return Math.sqrt(squareDiffs.reduce((a, b) => a + b, 0) / arr.length); } report() { console.table(this.results); }}// 使用示例const benchmark = new PerformanceBenchmark();benchmark.benchmark('Grayscale Conversion', () => { const src = TestUtils.createTestMat(640, 480, cv.CV_8UC3); const dst = new cv.Mat(); cv.cvtColor(src, dst, cv.COLOR_RGBA2GRAY); src.delete(); dst.delete();});benchmark.benchmark('Edge Detection', () => { const src = TestUtils.createTestMat(640, 480, cv.CV_8UC1); const dst = new cv.Mat(); cv.Canny(src, dst, 50, 100); src.delete(); dst.delete();});benchmark.report();4. 内存泄漏检测内存监控工具class MemoryMonitor { constructor() { this.snapshots = []; this.isMonitoring = false; } start() { this.isMonitoring = true; this.takeSnapshot(); this.intervalId = setInterval(() => { this.takeSnapshot(); }, 1000); } stop() { this.isMonitoring = false; if (this.intervalId) { clearInterval(this.intervalId); } } takeSnapshot() { if (performance.memory) { const snapshot = { timestamp: Date.now(), usedJSHeapSize: performance.memory.usedJSHeapSize, totalJSHeapSize: performance.memory.totalJSHeapSize, jsHeapSizeLimit: performance.memory.jsHeapSizeLimit }; this.snapshots.push(snapshot); // 检测内存增长 if (this.snapshots.length > 10) { const recent = this.snapshots.slice(-10); const growth = recent[recent.length - 1].usedJSHeapSize - recent[0].usedJSHeapSize; if (growth > 10 * 1024 * 1024) { // 10MB 增长 console.warn('Possible memory leak detected:', growth / 1024 / 1024, 'MB'); } } } } report() { console.table(this.snapshots); }}// 使用示例const memoryMonitor = new MemoryMonitor();memoryMonitor.start();// 运行测试function testMemoryLeak() { for (let i = 0; i < 1000; i++) { let mat = new cv.Mat(100, 100, cv.CV_8UC3); // 忘记释放 mat }}testMemoryLeak();memoryMonitor.stop();memoryMonitor.report();5. 调试工具可视化调试class DebugVisualizer { constructor(canvasId) { this.canvas = document.getElementById(canvasId); this.ctx = this.canvas.getContext('2d'); } visualizeMat(mat, title) { console.log(`Visualizing: ${title}`); console.log(`Size: ${mat.rows}x${mat.cols}`); console.log(`Type: ${mat.type()}`); console.log(`Channels: ${mat.channels()}`); // 显示图像 cv.imshow(this.canvas.id, mat); // 显示直方图 this.showHistogram(mat); } showHistogram(mat) { const hist = new cv.Mat(); const histSize = [256]; const ranges = [0, 256]; try { cv.calcHist([mat], [0], new cv.Mat(), hist, histSize, ranges); // 绘制直方图 const canvas = document.createElement('canvas'); canvas.width = 256; canvas.height = 100; const ctx = canvas.getContext('2d'); const max = Math.max(...hist.data32F); const scale = 100 / max; ctx.fillStyle = '#000'; ctx.fillRect(0, 0, 256, 100); ctx.fillStyle = '#0f0'; for (let i = 0; i < 256; i++) { const height = hist.data32F[i] * scale; ctx.fillRect(i, 100 - height, 1, height); } document.body.appendChild(canvas); } finally { hist.delete(); } } logMatInfo(mat) { console.group('Mat Information'); console.log('Rows:', mat.rows); console.log('Cols:', mat.cols); console.log('Type:', mat.type()); console.log('Channels:', mat.channels()); console.log('Depth:', mat.depth()); console.log('Element size:', mat.elemSize1()); console.log('Total elements:', mat.total()); console.log('Data pointer:', mat.data); console.groupEnd(); }}错误追踪class ErrorTracker { constructor() { this.errors = []; this.setupGlobalErrorHandling(); } setupGlobalErrorHandling() { window.addEventListener('error', (event) => { this.logError('Global Error', event.message, event.filename, event.lineno); }); window.addEventListener('unhandledrejection', (event) => { this.logError('Unhandled Promise Rejection', event.reason); }); } logError(type, message, filename = '', lineno = 0) { const error = { type, message, filename, lineno, timestamp: new Date().toISOString(), stack: new Error().stack }; this.errors.push(error); console.error('[ErrorTracker]', error); } trackOpenCVError(fn) { try { fn(); } catch (error) { this.logError('OpenCV Error', error.message); throw error; } } report() { console.group('Error Report'); this.errors.forEach((error, index) => { console.group(`Error ${index + 1}: ${error.type}`); console.log('Message:', error.message); console.log('Timestamp:', error.timestamp); if (error.filename) { console.log('File:', error.filename, 'Line:', error.lineno); } if (error.stack) { console.log('Stack:', error.stack); } console.groupEnd(); }); console.groupEnd(); }}6. 自动化测试流程完整的测试套件class OpenCVTestSuite { constructor() { this.tests = []; this.results = []; } addTest(name, testFn) { this.tests.push({ name, testFn }); } async runAll() { console.log('Starting OpenCV.js Test Suite...'); for (const test of this.tests) { try { await this.runTest(test); } catch (error) { this.results.push({ name: test.name, status: 'FAILED', error: error.message }); } } this.report(); } async runTest(test) { console.log(`Running: ${test.name}`); const start = performance.now(); try { await test.testFn(); const duration = performance.now() - start; this.results.push({ name: test.name, status: 'PASSED', duration }); console.log(`✓ ${test.name} (${duration.toFixed(2)}ms)`); } catch (error) { throw error; } } report() { const passed = this.results.filter(r => r.status === 'PASSED').length; const failed = this.results.filter(r => r.status === 'FAILED').length; console.log('\n=== Test Summary ==='); console.log(`Total: ${this.results.length}`); console.log(`Passed: ${passed}`); console.log(`Failed: ${failed}`); console.log(`Success Rate: ${((passed / this.results.length) * 100).toFixed(2)}%`); if (failed > 0) { console.log('\nFailed Tests:'); this.results .filter(r => r.status === 'FAILED') .forEach(r => console.log(`✗ ${r.name}: ${r.error}`)); } }}// 使用示例const testSuite = new OpenCVTestSuite();testSuite.addTest('Grayscale Conversion', async () => { const src = TestUtils.createTestMat(100, 100, cv.CV_8UC3); const dst = new cv.Mat(); try { cv.cvtColor(src, dst, cv.COLOR_RGBA2GRAY); expect(dst.channels()).toBe(1); } finally { src.delete(); dst.delete(); }});testSuite.addTest('Edge Detection', async () => { const src = TestUtils.createTestMat(100, 100, cv.CV_8UC1); const dst = new cv.Mat(); try { cv.Canny(src, dst, 50, 100); expect(dst.type()).toBe(cv.CV_8UC1); } finally { src.delete(); dst.delete(); }});testSuite.runAll();7. 调试最佳实践1. 启用详细日志cv['onRuntimeInitialized'] = () => { console.log('OpenCV.js Runtime Initialized'); console.log('Build Information:'); console.log(cv.getBuildInformation());};2. 使用断言function assert(condition, message) { if (!condition) { throw new Error(`Assertion failed: ${message}`); }}function processImage(src) { assert(src !== null, 'Source image cannot be null'); assert(src.rows > 0, 'Image must have positive height'); assert(src.cols > 0, 'Image must have positive width'); // 处理逻辑}3. 分步调试function debugProcess(src) { const steps = []; // 步骤 1:转灰度 const gray = new cv.Mat(); cv.cvtColor(src, gray, cv.COLOR_RGBA2GRAY); steps.push({ name: 'Grayscale', mat: gray.clone() }); // 步骤 2:模糊 const blurred = new cv.Mat(); cv.GaussianBlur(gray, blurred, new cv.Size(5, 5), 0); steps.push({ name: 'Blurred', mat: blurred.clone() }); // 步骤 3:边缘检测 const edges = new cv.Mat(); cv.Canny(blurred, edges, 50, 100); steps.push({ name: 'Edges', mat: edges.clone() }); // 清理 gray.delete(); blurred.delete(); edges.delete(); return steps;}总结OpenCV.js 的测试和调试策略包括:单元测试:测试单个函数和方法的正确性集成测试:测试完整的图像处理流程性能测试:基准测试和性能监控内存管理:检测和防止内存泄漏调试工具:可视化和错误追踪自动化测试:完整的测试套件和持续集成通过这些策略,可以确保 OpenCV.js 应用的质量和性能。