欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

OpenCV實(shí)現(xiàn)車牌字符分割(C++)

 更新時(shí)間:2020年11月27日 15:47:04   作者:Steven·簡(jiǎn)談  
這篇文章主要為大家詳細(xì)介紹了OpenCV實(shí)現(xiàn)車牌字符分割,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下

之前的車牌定位中已經(jīng)獲取到了車牌的位置,并且對(duì)車牌進(jìn)行了提取。我們最終的目的是進(jìn)行車牌識(shí)別,在這之前需要將字符進(jìn)行分割,方便對(duì)每一個(gè)字符進(jìn)行識(shí)別,最后將其拼接后便是完整的車牌號(hào)碼。關(guān)于車牌定位可以看這篇文章: OpenCV車牌定位(C++) ,本文使用的圖片也是來(lái)自這里。

先來(lái)看一看原圖:

最左邊的漢字本來(lái)是 ,截取時(shí)只獲得了右邊一點(diǎn)點(diǎn)的部分,這與原圖和獲取方法都有關(guān),對(duì)于 川、滬… 這一類左右分開的字會(huì)經(jīng)常發(fā)生這類問(wèn)題,對(duì)方法進(jìn)行優(yōu)化后可以解決,這里暫時(shí)不進(jìn)行討論。

后面的字都是完整的,字符分割的過(guò)程不會(huì)受影響。首先來(lái)一波常規(guī)操作,為了更方便處理,將其變成灰度圖片:

分割的方法不止一種,最簡(jiǎn)單的就是多加點(diǎn)人工成分,按照大致寬度再微調(diào)進(jìn)行截取,但是這樣看似最快其實(shí)成本最高,只適用于單一的圖片,因此這種容錯(cuò)低且不夠自動(dòng)的方法就不考慮了。

目前我使用了兩種不同的方法,一種是進(jìn)行邊緣檢測(cè)再檢測(cè)輪廓,根據(jù)字符的輪廓特點(diǎn)篩選出字符;另一種就是像素值判斷,主要根據(jù)像素?cái)?shù)量使用水平映射截取寬度,垂直映射因?yàn)楦叨然疽恢戮筒恍枰?,方法于水平映射一樣?/p>

兩種方法我都寫在后面,根據(jù)需要自行復(fù)制。如果要使用像素值進(jìn)行判斷的話,就需要再將灰度圖轉(zhuǎn)換成二值化圖片,使用閾值分割就行了。若使用第一種用輪廓分割的方法,灰度圖和二值化圖片都可以,結(jié)果沒什么區(qū)別。

檢測(cè)輪廓進(jìn)行分割

邊緣檢測(cè)

對(duì)圖像進(jìn)行邊緣檢測(cè),這里采用的是 Canny 邊緣檢測(cè),處理后的結(jié)果如下:

可以看到每個(gè)字的邊緣都被描繪出來(lái)了,接下來(lái)就將每個(gè)字的輪廓獲取出來(lái)。

檢測(cè)輪廓

直接使用 findContours() 將所有輪廓提取出來(lái),再將其在原圖中畫出來(lái)看看效果:

可以看到不僅僅是每個(gè)字被框出來(lái)了,還有內(nèi)部以及圖像中表現(xiàn)特殊部分的輪廓也有,接下來(lái)我們就根據(jù)每個(gè)字的大致大小篩選出我們想要的結(jié)果:

這樣看起來(lái)是不是就成功了,然后根據(jù)輪廓位置將每個(gè)字提取出來(lái)就行了,不過(guò)在這里每個(gè)輪廓的前后順序不一定是圖像中的位置,這里我使用每個(gè)輪廓左上角橫坐標(biāo) x 的大小來(lái)排序。

完整代碼:

#include <iostream> 
#include <opencv2/highgui/highgui.hpp> 
#include <opencv2/imgproc.hpp>
#include <opencv2/imgproc/types_c.h>
#include <map>

using namespace std;
using namespace cv;

int main() {
 Mat img = imread("number.jpg");
 Mat gray_img;
 // 生成灰度圖像
 cvtColor(img, gray_img, CV_BGR2GRAY);
 // 高斯模糊
 Mat img_gau;
 GaussianBlur(gray_img, img_gau, Size(3, 3), 0, 0);
 // 閾值分割
 Mat img_seg;
 threshold(img_gau, img_seg, 0, 255, THRESH_BINARY + THRESH_OTSU);
 // 邊緣檢測(cè),提取輪廓
 Mat img_canny;
 Canny(img_seg, img_canny, 200, 100);
 vector<vector<Point>> contours;
 vector<Vec4i> hierarchy;
 findContours(img_canny, contours, hierarchy, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE, Point());
 int size = (int)(contours.size());
 // 保存符號(hào)邊框的序號(hào)
 vector<int> num_order;
 map<int, int> num_map;
 for (int i = 0; i < size; i++) {
 // 獲取邊框數(shù)據(jù)
 Rect number_rect = boundingRect(contours[i]);
 int width = number_rect.width;
 int height = number_rect.height;
 // 去除較小的干擾邊框,篩選出合適的區(qū)域
 if (width > img.cols/10 && height > img.rows/2) {
 rectangle(img_seg, number_rect.tl(), number_rect.br(), Scalar(255, 255, 255), 1, 1, 0);
 num_order.push_back(number_rect.x);
 num_map[number_rect.x] = i;
 }
 }
 // 按符號(hào)順序提取
 sort(num_order.begin(), num_order.end());
 for (int i = 0; i < num_order.size(); i++) {
 Rect number_rect = boundingRect(contours[num_map.find(num_order[i])->second]);
 Rect choose_rect(number_rect.x, 0, number_rect.width, gray_img.rows);
 Mat number_img = gray_img(choose_rect);
 imshow("number" + to_string(i), number_img);
 // imwrite("number" + to_string(i) + ".jpg", number_img);
 }
 imshow("添加方框", gray_img);
 waitKey(0);
 return 0;
}

像素值判斷進(jìn)行分割

分割方法:首先判斷每一列的像素值大于 0 的像素個(gè)數(shù)超過(guò)5個(gè)時(shí),認(rèn)為此列是有數(shù)字的,記錄每列像素是否大于 5,產(chǎn)生一個(gè)數(shù)組。

// 確認(rèn)為 1 的像素
 int pixrow[1000];
 for (int i = 0; i < roi_col - 1; i++) {
 for (int j = 0; j < roi_row - 1; j++) {
 pix = img_threadhold.at<uchar>(j, i);
 pixrow[i] = 0;
 if (pix > 0) {
 pixrow[i] = 1;
 break;
 }
 }
 }
 // 對(duì)數(shù)組進(jìn)行濾波,減少突變概率
 for (int i = 2; i < roi_col - 1 - 2; i++) {
 if ((pixrow[i - 1] + pixrow[i - 2] + pixrow[i + 1] + pixrow[i + 2]) >= 3) {
 pixrow[i] = 1;
 }
 else if ((pixrow[i - 1] + pixrow[i - 2] + pixrow[i + 1] + pixrow[i + 2]) <= 1) {
 pixrow[i] = 0;
 }
 }

之后記錄像素為 0 和 1 所連續(xù)的長(zhǎng)度來(lái)計(jì)算字符的寬度,最后用寬度的大小來(lái)篩選字符。

// 確認(rèn)字符位置
 int count = 0;
 bool flage = false;
 for (int i = 0; i < roi_col - 1; i++) {
 pix = pixrow[i];
 if (pix == 1 && !flage) {
 flage = true;
 position1[count] = i;
 continue;
 }
 if (pix == 0 && flage) {
 flage = false;
 position2[count] = i;
 count++;
 }
 if (i == (roi_col - 2) && flage) {
 flage = false;
 position2[count] = i;
 count++;
 }
 }

分割出的結(jié)果:

完整代碼:

#include <iostream> 
#include <opencv2/highgui/highgui.hpp> 
#include <opencv2/imgproc.hpp>
#include <opencv2/imgproc/types_c.h>

using namespace std;
using namespace cv;

int main() {
 Mat img = imread("number.jpg");
 Mat gray_img;
 // 生成灰度圖像
 cvtColor(img, gray_img, CV_BGR2GRAY);
 // 高斯模糊
 Mat img_gau;
 GaussianBlur(gray_img, img_gau, Size(3, 3), 0, 0);
 // 閾值分割
 Mat img_threadhold;
 threshold(img_gau, img_threadhold, 0, 255, THRESH_BINARY + THRESH_OTSU);
 // 判斷字符水平位置
 int roi_col = img_threadhold.cols, roi_row = img_threadhold.rows, position1[50], position2[50], roi_width[50];
 uchar pix;
 // 確認(rèn)為 1 的像素
 int pixrow[1000];
 for (int i = 0; i < roi_col - 1; i++) {
 for (int j = 0; j < roi_row - 1; j++) {
 pix = img_threadhold.at<uchar>(j, i);
 pixrow[i] = 0;
 if (pix > 0) {
 pixrow[i] = 1;
 break;
 }
 }
 }
 // 對(duì)數(shù)組進(jìn)行濾波,減少突變概率
 for (int i = 2; i < roi_col - 1 - 2; i++) {
 if ((pixrow[i - 1] + pixrow[i - 2] + pixrow[i + 1] + pixrow[i + 2]) >= 3) {
 pixrow[i] = 1;
 }
 else if ((pixrow[i - 1] + pixrow[i - 2] + pixrow[i + 1] + pixrow[i + 2]) <= 1) {
 pixrow[i] = 0;
 }
 }
 // 確認(rèn)字符位置
 int count = 0;
 bool flage = false;
 for (int i = 0; i < roi_col - 1; i++) {
 pix = pixrow[i];
 if (pix == 1 && !flage) {
 flage = true;
 position1[count] = i;
 continue;
 }
 if (pix == 0 && flage) {
 flage = false;
 position2[count] = i;
 count++;
 }
 if (i == (roi_col - 2) && flage) {
 flage = false;
 position2[count] = i;
 count++;
 }
 }
 // 記錄所有字符寬度
 for (int n = 0; n < count; n++) {
 roi_width[n] = position2[n] - position1[n];
 }
 // 減去最大值、最小值,計(jì)算平均值用字符寬度來(lái)篩選
 int max = roi_width[0], max_index = 0;
 int min = roi_width[0], min_index = 0;
 for (int n = 1; n < count; n++) {
 if (max < roi_width[n]) {
 max = roi_width[n];
 max_index = n;
 }
 if (min > roi_width[n]) {
 min = roi_width[n];
 min_index = n;
 }
 }
 int index = 0;
 int new_roi_width[50];
 for (int i = 0; i < count; i++) {
 if (i == min_index || i == max_index) {}
 else {
 new_roi_width[index] = roi_width[i];
 index++;
 }
 }
 // 取后面三個(gè)值的平均值
 int avgre = (int)((new_roi_width[count - 3] + new_roi_width[count - 4] + new_roi_width[count - 5]) / 3.0);
 // 字母位置信息確認(rèn),用寬度來(lái)篩選
 int licenseX[10], licenseW[10], licenseNum = 0;
 int countX = 0;
 for (int i = 0; i < count; i++) {
 if (roi_width[i] >(avgre - 8) && roi_width[i] < (avgre + 8)) {
 licenseX[licenseNum] = position1[i];
 licenseW[licenseNum] = roi_width[i];
 licenseNum++;
 countX++;
 continue;
 }
 if (roi_width[i] > (avgre * 2 - 10) && roi_width[i] < (avgre * 2 + 10)) {
 licenseX[licenseNum] = position1[i];
 licenseW[licenseNum] = roi_width[i];
 licenseNum++;
 }
 }

 // 截取字符
 Mat number_img = Mat(Scalar(0));
 for (int i = 0; i < countX; i++) {
 Rect choose_rect(licenseX[i], 0, licenseW[i], gray_img.rows);
 number_img = gray_img(choose_rect);
 imshow("number" + to_string(i), number_img);
 // imwrite("number" + to_string(i) + ".jpg", number_img);
 }
 waitKey(0);
 return 0;
}

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • C語(yǔ)言中const和指針的秘密你知道嗎

    C語(yǔ)言中const和指針的秘密你知道嗎

    這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言中const和指針的秘密,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來(lái)幫助
    2022-02-02
  • 基于歐幾里德算法的使用

    基于歐幾里德算法的使用

    本篇文章介紹了,基于歐幾里德算法的使用。需要的朋友參考下
    2013-05-05
  • C++實(shí)現(xiàn)區(qū)塊鏈的源碼

    C++實(shí)現(xiàn)區(qū)塊鏈的源碼

    這篇文章主要介紹了C++實(shí)現(xiàn)區(qū)塊鏈的源碼,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-01-01
  • C語(yǔ)言簡(jiǎn)易掃雷游戲

    C語(yǔ)言簡(jiǎn)易掃雷游戲

    這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言簡(jiǎn)易掃雷游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2019-10-10
  • C++ Strassen算法代碼的實(shí)現(xiàn)

    C++ Strassen算法代碼的實(shí)現(xiàn)

    這篇文章主要介紹了C++ Strassen算法代碼的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-03-03
  • WM_CLOSE、WM_DESTROY、WM_QUIT及各種消息投遞函數(shù)詳解

    WM_CLOSE、WM_DESTROY、WM_QUIT及各種消息投遞函數(shù)詳解

    這篇文章主要介紹了WM_CLOSE、WM_DESTROY、WM_QUIT及各種消息投遞函數(shù),有助于讀者更好的理解windows程序的消息機(jī)制,需要的朋友可以參考下
    2014-07-07
  • VC中Tab control控件的用法詳細(xì)解析

    VC中Tab control控件的用法詳細(xì)解析

    以下是對(duì)VC中Tab control控件的用法進(jìn)行了詳細(xì)的介紹,需要的朋友可以過(guò)來(lái)參考下哦
    2013-09-09
  • C++ Qt開發(fā)之使用QHostInfo查詢主機(jī)地址

    C++ Qt開發(fā)之使用QHostInfo查詢主機(jī)地址

    Qt 是一個(gè)跨平臺(tái)C++圖形界面開發(fā)庫(kù),利用Qt可以快速開發(fā)跨平臺(tái)窗體應(yīng)用程序,本文將重點(diǎn)介紹如何運(yùn)用QHostInfo組件實(shí)現(xiàn)對(duì)主機(jī)地址查詢功能,希望對(duì)大家有所幫助
    2024-03-03
  • QT實(shí)現(xiàn)簡(jiǎn)單計(jì)算器功能

    QT實(shí)現(xiàn)簡(jiǎn)單計(jì)算器功能

    這篇文章主要為大家詳細(xì)介紹了QT實(shí)現(xiàn)簡(jiǎn)單計(jì)算器功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-04-04
  • C與C++動(dòng)態(tài)分配二維數(shù)組的實(shí)現(xiàn)方法

    C與C++動(dòng)態(tài)分配二維數(shù)組的實(shí)現(xiàn)方法

    下面小編就為大家?guī)?lái)一篇C與C++動(dòng)態(tài)分配二維數(shù)組的實(shí)現(xiàn)方法。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2016-12-12

最新評(píng)論