別實(shí)戰(zhàn):從硬件搭建到解碼優(yōu)化)
簡(jiǎn)介這份資源是面向嵌入式視覺(jué)與條碼識(shí)別開(kāi)發(fā)者的STM32H750端程序解決的是在MCU側(cè)通過(guò)OV5640攝像頭采集640×480分辨率RGB圖像并上傳至上位機(jī)完成一維碼、二維碼解碼的問(wèn)題。適合已具備STM32基礎(chǔ)、希望打通圖像采集與上位機(jī)解碼鏈路的工程師與高年級(jí)學(xué)生可作為條碼識(shí)別項(xiàng)目的參考實(shí)現(xiàn)。壓縮包共257個(gè)文件約16.77MB以C源碼與頭文件為主體包含39個(gè)c、67個(gè)h并配有40個(gè)d依賴文件、40個(gè)o目標(biāo)文件、40個(gè)su編譯單元記錄以及mk、makefile、ld鏈接腳本、map映射文件、bin與elf可執(zhí)行產(chǎn)物、ioc與mxproject工程配置等完整保留了從源碼到固件的構(gòu)建痕跡。目前已有588人學(xué)習(xí)下載。讀者可據(jù)此理解OV5640驅(qū)動(dòng)、DCMI與DMA圖像搬運(yùn)、串口上傳協(xié)議等關(guān)鍵環(huán)節(jié)并配合上位機(jī)解碼軟件完成一維碼與二維碼識(shí)別驗(yàn)證快速搭建可復(fù)現(xiàn)的條碼識(shí)別實(shí)驗(yàn)環(huán)境。1. 從一顆 H750 和一顆 OV5640 說(shuō)起條碼識(shí)別為什么要自己搭前陣子幫朋友調(diào)一套產(chǎn)線掃碼工裝需求很樸素固定工位、固定距離、掃一維碼和二維碼識(shí)別結(jié)果通過(guò)串口吐給上位機(jī)。他一開(kāi)始想用現(xiàn)成的模組后來(lái)發(fā)現(xiàn)視野和補(bǔ)光都不好調(diào)索性自己畫(huà)了塊板子主控選了 STM32H750VBT6攝像頭用 OV5640軟件層面就是一套跑在裸機(jī)上的條碼識(shí)別。這套組合在工業(yè)現(xiàn)場(chǎng)其實(shí)很常見(jiàn)H750 主頻能到 480MHz帶 DCMI 接口和硬件 JPEG 解碼OV5640 又是資料最全的 500 萬(wàn)像素 sensor 之一兩者湊一起做條碼識(shí)別成本和可控性都比買模組劃算。這篇文章就圍繞 STM32H750VBT6 加 OV5640 做條碼識(shí)別這條路線展開(kāi)把從硬件接線、DCMI 配置、圖像預(yù)處理到條碼解碼的完整鏈路講清楚。適合兩類人看一類是剛接觸 STM32 圖像采集、想找個(gè)具體項(xiàng)目練手的新手另一類是在產(chǎn)線或自助設(shè)備上做掃碼方案、需要評(píng)估自研可行性的工程師。我不會(huì)假設(shè)你手里有某個(gè)現(xiàn)成的工程包所有步驟都按通用做法給你照著搭就能跑起來(lái)。2. 硬件鏈路怎么搭DCMI、I2C 和時(shí)鐘的三角關(guān)系2.1 OV5640 與 H750 的引腳映射邏輯OV5640 輸出的是并行 DVP 信號(hào)包含 PCLK、HREF、VSYNC 和 8 位數(shù)據(jù)線 D0-D7另外還有 SCCB 控制接口本質(zhì)就是 I2C。H750 這邊對(duì)應(yīng)的是 DCMI 外設(shè)加一路 I2C。接線的時(shí)候最容易翻車的地方是數(shù)據(jù)線順序很多人以為 D0 必須接 DCMI_D0其實(shí) DCMI 支持?jǐn)?shù)據(jù)線重映射你接成 D7 到 DCMI_D0 也能通過(guò)配置糾正過(guò)來(lái)但前提是你得在 DCMI 初始化里把 DataWidth 和實(shí)際接線對(duì)齊。我一般會(huì)先把 OV5640 的引腳按功能分組電源組AVDD、DVDD、DOVDD、時(shí)鐘組XVCLK、PCLK、同步組VSYNC、HREF、數(shù)據(jù)組D0-D7、控制組SIOC、SIOD、PWDN、RESET。H750 這邊 DCMI 的引腳是固定的幾個(gè) AF 口查數(shù)據(jù)手冊(cè)的復(fù)用表就能確定。XVCLK 需要外部提供 24MHz 時(shí)鐘可以用 H750 的 MCO 輸出也可以外掛晶振我傾向用 MCO省一個(gè)物料。注意OV5640 的 PWDN 和 RESET 不要懸空很多模塊默認(rèn)拉低使能但自己畫(huà)板時(shí)如果懸空上電時(shí)序會(huì)亂表現(xiàn)為 I2C 讀不到 ID。2.2 DCMI 初始化從寄存器到 HAL 配置DCMI 的配置核心是幾個(gè)參數(shù)捕獲模式、數(shù)據(jù)寬度、時(shí)鐘極性、硬件同步。下面是一段基于 HAL 庫(kù)的初始化代碼我把它拆成可讀的片段。// DCMI 句柄配置重點(diǎn)在 hdcmi.Init 這幾個(gè)字段 hdcmi.Instance DCMI; hdcmi.Init.SynchroMode DCMI_SYNCHRO_HARDWARE; // 硬件同步用 VSYNC/HREF hdcmi.Init.PCKPolarity DCMI_PCKPOLARITY_RISING; // PCLK 上升沿采樣 hdcmi.Init.VSPolarity DCMI_VSPOLARITY_HIGH; // VSYNC 高電平有效 hdcmi.Init.HSPolarity DCMI_HSPOLARITY_HIGH; // HREF 高電平有效 hdcmi.Init.CaptureRate DCMI_CR_ALL_FRAME; // 全幀捕獲 hdcmi.Init.ExtendedDataMode DCMI_EXTEND_DATA_8B; // 8 位數(shù)據(jù) hdcmi.Init.JPEGMode DCMI_JPEG_DISABLE; // 條碼識(shí)別用 RGB 或 YUV不開(kāi) JPEG if (HAL_DCMI_Init(hdcmi) ! HAL_OK) { Error_Handler(); }這段代碼里最需要根據(jù)實(shí)際硬件調(diào)的是 PCKPolarity 和 VSPolarity。OV5640 默認(rèn)輸出 PCLK 上升沿有效但如果你走線比較長(zhǎng)、信號(hào)有振鈴可能得改成下降沿采樣才穩(wěn)。VSPolarity 取決于你用的是 VSYNC 高電平表示幀有效還是低電平這個(gè)在 OV5640 的寄存器 0x4740 里可以配兩邊必須一致否則你會(huì)看到圖像上下翻滾或者只采到半幀。參數(shù)說(shuō)明CaptureRate 設(shè)成全幀如果你只做條碼識(shí)別其實(shí)可以設(shè)成 DCMI_CR_1_LINE 配合行中斷做逐行處理省內(nèi)存但調(diào)試階段建議先全幀確認(rèn)圖像正常再優(yōu)化。ExtendedDataMode 選 8B因?yàn)?OV5640 的 DVP 就是 8 位輸出別選成 10B 或 12B否則數(shù)據(jù)錯(cuò)位。2.3 時(shí)鐘樹(shù)配置480MHz 主頻下怎么分頻給 DCMIH750 的 DCMI 時(shí)鐘來(lái)自 AHB 總線最高不能超過(guò) 100MHz 左右具體看手冊(cè)。我一般把系統(tǒng)時(shí)鐘跑到 480MHzAHB 分頻到 240MHzDCMI 再分頻到 80MHz 左右。OV5640 在 640x480 分辨率下 PCLK 大概 24MHz 到 48MHzDCMI 時(shí)鐘要高于 PCLK 才能穩(wěn)定采樣所以 80MHz 是夠的。配置的時(shí)候用 CubeMX 最省事但你要知道它在做什么。下面是手動(dòng)配置 DCMI 時(shí)鐘的關(guān)鍵寄存器操作// 假設(shè) AHB 已經(jīng) 240MHzDCMI 分頻系數(shù)設(shè)為 3得到 80MHz RCC-D2CCIP2R ~RCC_D2CCIP2R_DCMISEL; // 清除 DCMI 時(shí)鐘源選擇 RCC-D2CCIP2R | RCC_D2CCIP2R_DCMISEL_0; // 選 PLL2 作為時(shí)鐘源 // 然后在 DCMI 的 CR 寄存器里設(shè)置分頻 DCMI-CR ~DCMI_CR_EDM_Msk; DCMI-CR | (0x2 DCMI_CR_EDM_Pos); // 這里只是示例實(shí)際分頻在 RCC 里更穩(wěn)妥的做法是直接用 HAL_RCCEx_PeriphCLKConfig 配 PeriphClockSelection 為 RCC_PERIPHCLK_DCMI指定 ClockSelection 和 Divider。分頻系數(shù)別設(shè)太小DCMI 時(shí)鐘過(guò)高會(huì)導(dǎo)致采樣窗口變窄圖像上出現(xiàn)隨機(jī)噪點(diǎn)這種噪點(diǎn)在條碼識(shí)別里是致命的因?yàn)槎祷笤朦c(diǎn)會(huì)變成假條空。3. 圖像預(yù)處理從 RAW 到能二值化的灰度圖3.1 OV5640 輸出格式選擇RGB565 還是 YUVOV5640 支持 RGB565、YUV422、JPEG 等輸出格式。做條碼識(shí)別我建議用 YUV422因?yàn)?Y 分量就是灰度直接拿來(lái)做二值化省掉 RGB 轉(zhuǎn)灰度的計(jì)算。如果你用 RGB565就得在 MCU 里做一次加權(quán)平均H750 雖然有硬件加速但沒(méi)必要多這一步。配置輸出格式是通過(guò) SCCB 寫(xiě)寄存器OV5640 的寄存器 0x4300 控制輸出順序0x501F 控制 ISP 輸出格式。下面是一段寫(xiě)寄存器的封裝// 通過(guò) SCCB 寫(xiě) OV5640 寄存器reg 是 16 位地址val 是 8 位值 uint8_t OV5640_WriteReg(uint16_t reg, uint8_t val) { uint8_t buf[3]; buf[0] (reg 8) 0xFF; buf[1] reg 0xFF; buf[2] val; return HAL_I2C_Master_Transmit(hi2c1, OV5640_ADDR, buf, 3, 100); } // 設(shè)置為 YUV422 輸出 OV5640_WriteReg(0x4300, 0x30); // YUV422 OV5640_WriteReg(0x501F, 0x03); // ISP 輸出 YUV422邏輯說(shuō)明0x4300 的 0x30 表示 YUYV 順序0x501F 的 0x03 是 ISP 控制寄存器選 YUV422。寫(xiě)完這兩個(gè)之后DCMI 收到的數(shù)據(jù)就是 Y0 U0 Y1 V0 這樣的排列你只需要取每個(gè)像素對(duì)的第一個(gè)字節(jié)就是 Y 分量。參數(shù)說(shuō)明如果你發(fā)現(xiàn)圖像顏色不對(duì)但灰度正常說(shuō)明 YUV 順序反了把 0x4300 改成 0x32 試試。另外 OV5640 的自動(dòng)白平衡和自動(dòng)增益在條碼場(chǎng)景下最好關(guān)掉因?yàn)闂l碼是黑白對(duì)比自動(dòng)增益會(huì)把對(duì)比度拉低反而不好二值化。關(guān) AWB 是寫(xiě) 0x3406 為 0x00關(guān) AGC 是寫(xiě) 0x3503 為 0x00。3.2 灰度化與二值化在 MCU 上怎么算得快如果你已經(jīng)用了 YUV422灰度化這步就省了。接下來(lái)是二值化最常用的是全局閾值比如大津法。但在 H750 上跑大津法一幀 640x480 要遍歷兩次直方圖耗時(shí)大概幾毫秒可以接受。下面是一個(gè)簡(jiǎn)化版的大津法實(shí)現(xiàn)// 輸入灰度圖指針寬高輸出閾值 uint8_t OtsuThreshold(uint8_t *img, int w, int h) { int hist[256] {0}; int total w * h; for (int i 0; i total; i) { hist[img[i]]; } float sum 0; for (int i 0; i 256; i) { sum i * hist[i]; } float sumB 0, wB 0, maxVar 0; uint8_t threshold 0; for (int i 0; i 256; i) { wB hist[i]; if (wB 0) continue; float wF total - wB; if (wF 0) break; sumB i * hist[i]; float mB sumB / wB; float mF (sum - sumB) / wF; float varBetween wB * wF * (mB - mF) * (mB - mF); if (varBetween maxVar) { maxVar varBetween; threshold i; } } return threshold; }這段代碼的邏輯是遍歷直方圖找類間方差最大的閾值。參數(shù)說(shuō)明img 是 Y 分量數(shù)組w 和 h 是圖像寬高。實(shí)際用的時(shí)候我建議先對(duì)圖像做一次 2x2 降采樣把 640x480 降到 320x240這樣直方圖統(tǒng)計(jì)量少四分之三閾值依然夠用因?yàn)闂l碼的條空寬度通常大于 2 個(gè)像素。二值化之后條碼區(qū)域是黑白分明的但背景可能有反光或陰影。這時(shí)候可以加一個(gè)形態(tài)學(xué)開(kāi)運(yùn)算去掉小的噪點(diǎn)。在 MCU 上做形態(tài)學(xué)用 3x3 的核就夠了腐蝕一次再膨脹一次代碼不復(fù)雜但要注意邊界處理別越界訪問(wèn)。3.3 圖像緩存怎么放內(nèi)部 SRAM 還是外部 SDRAMH750VBT6 內(nèi)部 SRAM 有 1MB 左右但分散在幾個(gè)域里DCMI 能直接寫(xiě)入的是 AXI SRAM 和部分 D2 域 SRAM。一幀 640x480 的 YUV422 是 614KB內(nèi)部 SRAM 放不下兩幀只能放一幀。如果你要做雙緩沖就得外掛 SDRAM 或者用 JPEG 壓縮。我的做法是調(diào)試階段用單緩沖DCMI 配置成快照模式采完一幀就停處理完再采下一幀。這樣雖然幀率低但邏輯簡(jiǎn)單不容易出錯(cuò)。等識(shí)別率穩(wěn)定了再考慮外掛 SDRAM 做乒乓緩沖。H750 的 FMC 接口掛一片 16 位 SDRAM成本增加不多但能跑到 30fps 以上。注意DCMI 寫(xiě)入 SDRAM 時(shí)要確保 SDRAM 的刷新周期和 DCMI 的突發(fā)傳輸匹配否則會(huì)出現(xiàn)圖像撕裂。常見(jiàn)做法是把 DCMI 的 DMA 突發(fā)長(zhǎng)度設(shè)成 8 或 16SDRAM 的 CAS 延遲設(shè)成 2 或 3具體看你的 SDRAM 型號(hào)。4. 條碼解碼從二值圖到字符串的最后一公里4.1 一維碼掃描線找條空邊界一維碼解碼的核心是找條空寬度。我一般從圖像中間取若干條水平掃描線對(duì)每條線做游程編碼記錄連續(xù)黑像素和白像素的長(zhǎng)度。然后根據(jù)起始符和終止符判斷條碼類型比如 EAN-13 的起始符是 101Code128 的起始符是 211412。下面是一個(gè)游程編碼的片段// 對(duì)一行二值圖像做游程編碼返回游程數(shù)組和長(zhǎng)度 int RunLengthEncode(uint8_t *line, int width, int *runs, int maxRuns) { int count 0; int runLen 1; for (int i 1; i width; i) { if (line[i] line[i-1]) { runLen; } else { if (count maxRuns) runs[count] runLen; runLen 1; } } if (count maxRuns) runs[count] runLen; return count; }邏輯說(shuō)明line 是二值化后的一行0 表示黑1 表示白。runs 數(shù)組存的是每個(gè)連續(xù)段的長(zhǎng)度。參數(shù)說(shuō)明maxRuns 要設(shè)得足夠大比如 200因?yàn)橐痪S碼的條空數(shù)量可能上百。拿到 runs 之后你要做歸一化因?yàn)闂l碼的條空寬度是基準(zhǔn)寬度的整數(shù)倍通常用最小游程作為基準(zhǔn)然后看每個(gè)游程是基準(zhǔn)的幾倍四舍五入到整數(shù)。一維碼解碼的坑在于如果掃描線沒(méi)穿過(guò)條碼中心或者條碼有傾斜游程寬度會(huì)失真。解決辦法是多取幾條掃描線取解碼成功次數(shù)最多的結(jié)果。另外條碼兩側(cè)要有足夠的空白區(qū)如果圖像裁剪太緊起始符和終止符可能被切掉導(dǎo)致解碼失敗。4.2 二維碼定位圖案與網(wǎng)格采樣二維碼比如 QR Code的解碼比一維碼復(fù)雜但步驟是固定的找三個(gè)定位圖案確定版本和方向然后按網(wǎng)格采樣。在 MCU 上做完整的 QR 解碼代碼量不小我一般會(huì)移植一個(gè)輕量級(jí)的庫(kù)比如 quirc 或者自己寫(xiě)一個(gè)簡(jiǎn)化版。定位圖案的查找方法是在二值圖上做行掃描找 1:1:3:1:1 的黑白比例。找到三個(gè)之后用它們的位置關(guān)系確定二維碼的四個(gè)角。下面是一個(gè)查找定位圖案的偽代碼// 在二值圖中查找 QR 定位圖案返回候選點(diǎn) int FindFinderPattern(uint8_t *img, int w, int h, Point *candidates, int maxCand) { int count 0; for (int y 0; y h; y) { int run[5] {0}; int state 0; for (int x 0; x w; x) { if (img[y*wx] 0) { // 黑 if (state % 2 0) run[state]; else { state; run[state]; } } else { // 白 if (state % 2 1) run[state]; else { state; run[state]; } } if (state 5) { // 檢查比例是否接近 1:1:3:1:1 int total run[0]run[1]run[2]run[3]run[4]; if (abs(run[0]*7 - total) total/2 abs(run[1]*7 - total) total/2 abs(run[2]*3 - total*3/7) total/2 abs(run[3]*7 - total) total/2 abs(run[4]*7 - total) total/2) { if (count maxCand) { candidates[count].x x - total; candidates[count].y y; count; } } // 滑動(dòng)窗口繼續(xù)找 run[0] run[2]; run[1] run[3]; run[2] run[4]; state 3; } } } return count; }這段代碼的邏輯是維護(hù)一個(gè) 5 段游程窗口當(dāng)窗口滿 5 段時(shí)檢查比例。參數(shù)說(shuō)明img 是二值圖0 為黑1 為白。candidates 存候選定位圖案的中心坐標(biāo)。實(shí)際用的時(shí)候你還需要在垂直方向再驗(yàn)證一次避免把條碼的某些圖案誤判成定位圖案。找到三個(gè)定位圖案后計(jì)算二維碼的版本和模塊大小然后對(duì)每個(gè)模塊中心采樣得到 0/1 矩陣。最后按 QR 的格式做糾錯(cuò)和解碼。這部分如果自己寫(xiě)工作量不小建議先用現(xiàn)成庫(kù)跑通再根據(jù)需求裁剪。4.3 解碼結(jié)果輸出串口協(xié)議怎么定識(shí)別出字符串之后要通過(guò)串口發(fā)給上位機(jī)。協(xié)議我一般定得很簡(jiǎn)單幀頭 0xAA 0x55然后跟一個(gè)字節(jié)的長(zhǎng)度再跟數(shù)據(jù)最后跟一個(gè)字節(jié)的校驗(yàn)和。這樣上位機(jī)解析起來(lái)不容易錯(cuò)。// 發(fā)送條碼結(jié)果data 是字符串len 是長(zhǎng)度 void SendBarcodeResult(uint8_t *data, uint8_t len) { uint8_t header[2] {0xAA, 0x55}; uint8_t checksum 0; HAL_UART_Transmit(huart1, header, 2, 100); HAL_UART_Transmit(huart1, len, 1, 100); for (int i 0; i len; i) { checksum data[i]; } HAL_UART_Transmit(huart1, data, len, 100); HAL_UART_Transmit(huart1, checksum, 1, 100); }邏輯說(shuō)明先發(fā)幀頭再發(fā)長(zhǎng)度再發(fā)數(shù)據(jù)最后發(fā)校驗(yàn)和。參數(shù)說(shuō)明len 是條碼字符串的長(zhǎng)度一般不超過(guò) 64 字節(jié)。校驗(yàn)和用累加和簡(jiǎn)單夠用。如果你要傳中文或者二進(jìn)制數(shù)據(jù)長(zhǎng)度字段要改成兩字節(jié)并且數(shù)據(jù)里如果有 0xAA 0x55要做轉(zhuǎn)義否則上位機(jī)會(huì)誤判幀頭。5. 避坑與排查那些讓我熬夜的瞬間5.1 圖像全黑或全白先查時(shí)鐘和電源現(xiàn)象DCMI 采到的圖像要么全黑要么全白偶爾有幾行噪點(diǎn)。原因OV5640 的 XVCLK 沒(méi)起振或者 PWDN 引腳電平不對(duì)。解決用示波器量 XVCLK應(yīng)該是 24MHz 方波量 PWDN正常工作時(shí)應(yīng)該是低電平。如果 XVCLK 沒(méi)有檢查 MCO 配置或者換外掛晶振。5.2 圖像有橫條紋PCLK 極性反了現(xiàn)象圖像上有規(guī)律的橫條紋條紋間距和 PCLK 周期相關(guān)。原因DCMI 的 PCKPolarity 和 OV5640 的輸出極性不匹配。解決把 DCMI_Init.PCKPolarity 從 RISING 改成 FALLING或者反過(guò)來(lái)。這個(gè)坑很玄學(xué)因?yàn)橛袝r(shí)候條紋不明顯但二值化之后條碼就解不出來(lái)了。5.3 條碼識(shí)別率低補(bǔ)光和曝光時(shí)間沒(méi)調(diào)好現(xiàn)象同一張條碼有時(shí)候能解有時(shí)候解不出來(lái)。原因OV5640 的自動(dòng)曝光在條碼場(chǎng)景下會(huì)頻繁調(diào)整導(dǎo)致圖像亮度不穩(wěn)定。解決關(guān)掉自動(dòng)曝光手動(dòng)設(shè)曝光時(shí)間。寫(xiě)寄存器 0x3503 為 0x00 關(guān) AEC然后寫(xiě) 0x3500、0x3501、0x3502 設(shè)曝光值。具體值要根據(jù)你的補(bǔ)光強(qiáng)度調(diào)我一般從 0x1000 開(kāi)始試。5.4 解碼結(jié)果亂碼串口波特率或校驗(yàn)和錯(cuò)了現(xiàn)象上位機(jī)收到的字符串是亂碼或者校驗(yàn)和不對(duì)。原因串口波特率不匹配或者校驗(yàn)和計(jì)算時(shí)把長(zhǎng)度字節(jié)也算進(jìn)去了。解決確認(rèn)兩邊波特率一致校驗(yàn)和只累加數(shù)據(jù)部分不包括幀頭和長(zhǎng)度。另外如果數(shù)據(jù)里有 0x00HAL_UART_Transmit 是能發(fā)的但上位機(jī)如果按字符串處理會(huì)截?cái)嗨詤f(xié)議里最好帶長(zhǎng)度。5.5 幀率上不去DCMI 和 SDRAM 搶總線現(xiàn)象外掛 SDRAM 之后幀率反而比單緩沖還低。原因DCMI 的 DMA 和 CPU 同時(shí)訪問(wèn) SDRAM總線仲裁開(kāi)銷大。解決把 DCMI 的 DMA 優(yōu)先級(jí)設(shè)高或者用雙緩沖加 DMA 傳輸完成中斷讓 CPU 在另一塊緩沖上處理。另外SDRAM 的刷新周期別設(shè)太短否則占用太多帶寬。6. 進(jìn)階技巧把識(shí)別率從 90% 拉到 99%前面講的都是能跑通的基礎(chǔ)鏈路但實(shí)際產(chǎn)線上條碼可能有污損、傾斜、反光識(shí)別率卡在 90% 上不去。我自己的經(jīng)驗(yàn)是最后這 10% 靠的是多幀融合和參數(shù)自適應(yīng)。多幀融合的做法是連續(xù)采 3 幀分別二值化并解碼取多數(shù)結(jié)果。如果三幀結(jié)果不一致就取置信度最高的那個(gè)。置信度可以用解碼時(shí)的糾錯(cuò)次數(shù)來(lái)衡量糾錯(cuò)次數(shù)越少置信度越高。這個(gè)邏輯在 MCU 上實(shí)現(xiàn)不難就是多占點(diǎn)內(nèi)存。參數(shù)自適應(yīng)是指根據(jù)圖像質(zhì)量動(dòng)態(tài)調(diào)整二值化閾值和曝光。比如如果當(dāng)前幀的直方圖雙峰不明顯說(shuō)明對(duì)比度低就加大補(bǔ)光或者延長(zhǎng)曝光。下面是一個(gè)簡(jiǎn)單的判斷邏輯// 判斷直方圖是否雙峰返回 1 表示雙峰明顯 int IsBimodal(int *hist) { int peak1 0, peak2 0; int valley 255; for (int i 1; i 255; i) { if (hist[i] hist[peak1]) peak1 i; } for (int i 1; i 255; i) { if (abs(i - peak1) 30 hist[i] hist[peak2]) peak2 i; } if (peak2 0) return 0; int minIdx peak1 peak2 ? peak1 : peak2; int maxIdx peak1 peak2 ? peak1 : peak2; for (int i minIdx; i maxIdx; i) { if (hist[i] valley) valley hist[i]; } // 如果谷底值小于兩個(gè)峰值平均值的 1/3認(rèn)為雙峰明顯 return (valley (hist[peak1] hist[peak2]) / 6); }邏輯說(shuō)明先找兩個(gè)峰值再找它們之間的谷底如果谷底足夠低說(shuō)明黑白分明。參數(shù)說(shuō)明峰值間距閾值 30 是根據(jù)經(jīng)驗(yàn)定的你可以根據(jù)實(shí)際圖像調(diào)整。如果返回 0就觸發(fā)補(bǔ)光增強(qiáng)或曝光調(diào)整。還有一個(gè)技巧是 ROI 裁剪。如果條碼在圖像中的位置相對(duì)固定你可以只處理中間一塊區(qū)域比如 320x240這樣處理速度快一倍而且背景干擾少。ROI 的坐標(biāo)可以通過(guò)上位機(jī)配置存到 H750 的 Flash 里掉電不丟。最后說(shuō)一個(gè)我踩過(guò)的坑OV5640 的鏡頭焦距。很多模塊默認(rèn)是泛焦近距離掃碼時(shí)圖像是糊的。你得手動(dòng)擰鏡頭邊擰邊看串口輸出的識(shí)別率找到最清晰的位置。這個(gè)調(diào)整很費(fèi)時(shí)間但調(diào)好之后識(shí)別率會(huì)有明顯提升。我一般會(huì)在鏡頭上點(diǎn)一滴 UV 膠固定免得振動(dòng)跑焦。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取