前言
1.之前在PC端做過身份證檢測識別相關的項目,用的環境是Caffe-SSD訓練的VGG16,模型大小大概爲90M左右,在PC下,不調用GPU加速的話,處理檢測速度並不理想。之後想把這個項目移植到移動端,然後在IPhone XR Max 上做了測試,速度比PC端更慢了,而且體積巨大,根本沒有辦法應用到項目上。
2.爲了能在移動端運行目標檢測模型,那隻能重新訓練,看了一堆資料和測試各種官方Demo,之後選了MobileNetv2-SSDLite,訓練框架還是用Caffe。
模型訓練
1.關於前期的數據準備與數據樣本標註,可以看我之前身份證識別的博客,我訓練時還是用VOC2007這種數據格式。
2.MobileNetv2-SSDLite什麼訓練自己的數據集,可以看這個博客
,博主寫得很詳細。
3.在訓練過程中發現,同樣的數據集,同樣的迭代次數,caffe-ssd訓練出來的模型精度要高出MobileNetv2-SSDLite幾個百分點,而且MobileNetv2-SSDLite對特徵弱的物體識別很容易出現誤檢的現象,爲了精度能達到可用的級別,唯一的辦法是加樣本,但身份證這種數據集又比較敏感,很不好收集,想了各種辦法,才收集了一萬張左右的數據,再寫個仿真算法,把數據擴增到十萬張左右,迭代20萬代左右,精度可以達99.5%。
4.最終的模型大小在14M左右,我放了6個類型在裏面,在真機下檢測一張圖像的速度大概在0.02秒左右,基本上可以達到實時。
應用代碼
1.在OpenCV3之後的版本都有dnn這個模塊,很好的對接深度學習的模型,我這裏用的是OpenCV4.2這個版本,iOS是不支持直接顯示OpenCV的Mat這種圖像格式的,要把Mat轉成UIImage才能在iOS上顯示,關於轉換的代碼可以看我之前的博客。
2.OC是可以直接與C++交互的,所以檢測的代碼我直接用C++寫的。
代碼:
bool idDetection(cv::Mat &cv_src, cv::Mat &cv_dst, std::string &model_path, std::string &proto_path, std::vector<std::string> &label)
{
if (cv_src.empty())
{
return false;
}
cv_dst = cv_src.clone();
cv::Size reso(300, 300);
cv::dnn::Net net = cv::dnn::readNet(model_path,proto_path);
if (net.empty())
{
return false;
}
cv::Mat blob = cv::dnn::blobFromImage(cv_src, 1.0, reso, cv::Scalar(0, 0, 0), true, false);
net.setInput(blob);
cv::Mat out = net.forward();
cv::Mat detectionMat(out.size[2], out.size[3], CV_32F, out.ptr<float>());
float confThreshold = 0.25f;
float nmsThreshold = 0.5f;
std::vector<int> classIds;
std::vector<float> confidences;
std::vector<cv::Rect> boxes;
for (int i = 0; i < detectionMat.rows; i++)
{
float confidence = detectionMat.at<float>(i, 2);
if (confidence > confThreshold)
{
size_t objectClass = (size_t)(detectionMat.at<float>(i, 1));
int left = static_cast<int>(detectionMat.at<float>(i, 3) * cv_src.cols);
int top = static_cast<int>(detectionMat.at<float>(i, 4) * cv_src.rows);
int right = static_cast<int>(detectionMat.at<float>(i, 5) * cv_src.cols);
int bottom = static_cast<int>(detectionMat.at<float>(i, 6) * cv_src.rows);
int width = right - left + 1;
int height = bottom - top + 1;
classIds.push_back(objectClass);
boxes.push_back(cv::Rect(left, top, width, height));
confidences.push_back(confidence);
}
}
std::vector<int> indices;
cv::dnn::NMSBoxes(boxes, confidences, confThreshold, nmsThreshold, indices);
std::vector<int> id;
for (size_t i = 0; i < indices.size(); ++i)
{
int idx = indices[i];
cv::Rect box = boxes[idx];
rectangle(cv_dst, filter_ida.at(i).tl(), filter_ida.at(i).br(), cv::Scalar(0, 0, 255), 2, 8, 0);
//id.push_back(classIds[idx]);
}
}
3.在Xcode裏面,把要與C++交互的源碼文件.m更改成.mm,定義一個點擊事件,然後添加代碼:
-(void)idDetectioBtn
{
NSString* const model_file_name = @"inference";
NSString* const model_file_type = @"caffemodel";
NSString* const proto_file_name = @"inference";
NSString* const proto_file_type = @"prototxt";
NSString* model_path = [[NSBundle mainBundle] pathForResource:model_file_name ofType:model_file_type];
NSString* prototxt_path = [[NSBundle mainBundle] pathForResource:proto_file_name ofType:proto_file_type];
std::string str_proto = [prototxt_path UTF8String];
std::string str_model = [model_path UTF8String];
cv::Mat cv_src,cv_dst;
UIImageToMat(self.ui_selected_image, cv_src);
std::vector<std::string> id_label;
idDetection(cv_src, cv_dst, str_model, str_proto, id_label);
UIImage *ui_image = MatToUIImage(cv_dst);
self.ui_show_view.image = ui_image;
}
4.運行效果
注:
對圖像處理有興趣的可以可以加