+ P# F8 E) ?. f$ x$ n. I; u$ X. ^1 y3 @7 z# D: S; n- Q
项目创建以后 % o/ t" @ D9 b, sAfter the project is created, replace all of the contents of CMakeLists.txt file with the following (the comments in the following code are meant as a description to why each line exists at all): 8 D q$ C8 A5 l $ ] C* v/ v" d. k( r# R# Specify the minimum version of CMake(3.1 is currently recommended by Qt)& ]' ` {. Z/ ]* K$ u- ]
cmake_minimum_required(VERSION 3.1)( V; K6 m2 E: d3 _
, s0 {4 H0 X$ U4 o# Specify project title . U1 {: G. l* g( H# jproject(ImageClassifier) : J" B, V& m& u9 `7 N% ?. G+ H9 P0 q1 q$ \6 E2 R$ y8 X9 C* X
# To automatically run MOC when building(Meta Object Compiler) 7 j3 a2 u( r& l" lset(CMAKE_AUTOMOC ON) % n4 ]+ k! Z; V* r0 Z0 T* Q _" f- ?( n+ j
# To automatically run UIC when building(User Interface Compiler), w7 I* z* U7 V6 T; E) z
set(CMAKE_AUTOUIC ON)6 ~9 w6 }8 C7 B7 B: r4 \
1 W9 {1 Q! M8 p: c% |
# To automatically run RCC when building(Resource Compiler)& Q' p3 Y8 d- u- T
set(CMAKE_AUTORCC ON) 9 N8 {9 }0 Z. O) s# Q- N 7 ]$ H1 y5 g7 ^# A8 |2 j* N+ L# Specify OpenCV folder, and take care of dependenciesand includes & Q a7 l+ q# Nset(OpenCV_DIR "path_to_opencv") ' S$ H( ~/ X( j3 z+ nfind_package(OpenCV REQUIRED) 2 s" \, M {7 t( xinclude_directories(${ OpenCV_INCLUDE_DIRS }) 1 \+ e' L# o* ^: }4 R p; T2 Y; H8 p; \' y$ B
# Take care of Qt dependencies3 z* k8 H# D* e; \5 }5 t
find_package(Qt5 COMPONENTS Core Gui Widgets REQUIRED) 2 e2 E- X+ i$ |! j' ?, ~* w$ s) X1 j6 [, |6 E6 k
# add required source, header, uiand resource files ( C% g$ `! Q% t: ^$ \9 Jadd_executable(${ PROJECT_NAME } "main.cpp" "mainwindow.h" "mainwindow.cpp" "mainwindow.ui") ' l8 {2 K& w& n* j6 T- M# i) O& G1 {2 }
# link required libs . q4 ~- I: @# Q0 \8 U( ntarget_link_libraries(${PROJECT_NAME} Qt5::Core Qt5::Gui Qt5::Widgets ${OpenCV_LIBS})6 `" b3 p2 c B
1: g8 F; \: e5 I: s
2; O$ z4 x$ w, v9 r4 y
3 ; J2 Q; f0 ^+ W- m1 l$ k4' K( e- Y1 b7 l
5. o" r z1 f' j9 z0 x9 J Q
68 A$ J) C- Y- [0 Z
7 / w7 F& { j$ t$ M1 N) M3 s8 / X- m& Y0 P( {. C0 h9, q3 k, W3 t# n& j6 L) ~
104 v2 B; z) N* H7 a0 D" }
11 " H$ W3 q1 u* T3 W$ ?- j# p12* \$ t( ?5 Y# ^4 [0 ~
13 ; O. _. Z7 \3 Y; e4 k2 v146 \: C3 G, c, U- d: p
15& _- D7 q* T1 w+ \, G7 ?# j6 K
160 A: c8 T/ a5 E2 h6 x, A
17 3 Z; X- Q9 s) L: Y2 V0 ]18 6 g: c' L' \2 }2 N W" P19 9 W# O( f/ L; |0 `) q208 f. C9 {, d5 b1 K1 j2 I: P
21 4 ]$ R3 z ]1 r6 n22& E; r7 \# u6 R/ w* @
23 ! Q1 {; T- N- W, z* j! [: J249 r' `5 r, ^" X/ U
25 + O' z6 U/ Q6 X26! b9 o1 S/ U9 w. L* o+ y) D
27' T+ ~( J$ A: a( X
282 b1 A, ^+ e: @* O* G7 A3 \/ W
把里面正确的地址写好,把main.cpp 里面开始mainwindow.cpp 。 % ^( U' b: X; b: C* x : i0 ?5 v7 m" }* u3 a ]! K9 o写好main.cpp# L' R1 i9 l; G; g5 Q) v
#include "mainwindow.h"1 l9 K. o- A- _ R1 B/ O
#include <QApplication> $ H4 _& D7 S3 h6 f i( u2 I, x; y/ a2 M/ a# \, j. j% x
int main(int argc, char* argv[])& E6 W; P# ^7 g/ [& b, O F
{& V" M, e, l+ l" f' {
QApplication a(argc, argv);& _* j8 Y |; Q- H
MainWindow w;6 ]5 P! v$ n& |: W3 E% e- y. [
w.show();4 K0 k* g0 H! P: j. f2 \! D
# T P0 U2 {* D q8 E' c return a.exec();- j$ P+ m' F. u _
}# L5 a( K |7 Z9 w) O" e: n* C* q
1 % o0 p5 H' ]9 H2 m0 u/ ^3 t/ c. X/ }& X
3 r( R2 g0 j9 m0 Z0 X8 ]
4( q% R' i' D2 ~$ G: i9 C
59 c5 Q+ @7 v2 X% P1 K
6- t8 @4 S0 a; S+ e+ `
7 % @. X3 U' [. I" B, v8 o5 K8 Z% s" g' [1 i# e: R9: F4 \$ J! c( O' R
102 j/ r2 s; }3 e5 R$ d3 k# d( }, i
11/ f5 S% q# R B+ n" M
现在使用设计器加上一个窗体,: a2 C5 r$ a. ]# P" f& [5 k
7 Q& H- r/ G0 P( [3 W选择Main Window 8 J/ u$ Q, S; h0 @8 p+ g" X. a0 ?: ^" x& S7 _( X' f6 h. U, G
) l- N8 ]& w1 n: [6 E( u* Q. m" @
像下面这样设计窗体就行了8 M n9 [: J/ [ u( R
1 D+ H; ]( M2 A5 N+ o & S* i, a1 q" y3 w“mainwindow.h” 文件代码如下! G% m1 D& I; O" a
, e1 ?6 \9 P3 }# f- e' z
#include <QMainWindow> : h+ `6 b) K9 r3 x$ s( |#include <QMessageBox> + ?) H7 J6 M6 }1 E. L3 G4 O% _9 l# J#include <QDebug> & e* N% V2 r! O#include <QFile>7 A8 \$ A: y- K V+ D" a
#include <QElapsedTimer> 4 I' \8 s1 e$ B; a( S9 N8 J( F#include <QGraphicsScene> / {, K, n* }5 ]9 Z4 f#include <QGraphicsPixmapItem> 6 Y" l' ]- e" z( D& e) |#include <QCloseEvent> " s! ~+ Q+ K! ]8 i6 E( }8 h#include <QFileDialog>! B3 T) L( y7 T+ L2 j: ^
#include <opencv2/opencv.hpp>) C- D" Q) F9 N3 ~* x& A: d
We will also need the following private members: 8 c# }' W9 Z C1 ~. Q. e5 x9 b4 j: j7 p
cv::dnn::Net tfNetwork;5 u' X a% `+ P+ O, b% v/ o
QGraphicsScene scene;6 V3 Q! J+ H; j" F$ x: q( _
QGraphicsPixmapItem pixmap; . V; ?" \6 Q5 t$ X+ Ebool videoStopped; 8 H* o' R4 V0 _5 i0 g! T19 G: X5 b f, S3 P6 k
2 7 u. O. ~6 y x) I+ }3, W& v7 d) h5 w. ?. c
4 ! X% Z- p1 @$ u4 K0 k+ h. ^& A& Y$ t59 w8 Q0 {" h& y. I
6- c* R- o o2 w4 ?0 Y1 o1 u
7 ' w5 O- V7 u* l5 G3 q1 ^( T8( d8 U2 W$ Y; W
9 - y; Z; s* |! n2 Z10 7 x+ V! P; G+ s& n; n6 N5 o11 8 U+ x, G8 W8 r" w: V12- P% A" Z4 x* Z* H9 |
139 s( a. @0 [+ i; r1 D& n
14 0 \2 a6 Y, }6 n1 a151 U$ K7 W' m& [' i3 a
16" t3 ~4 x( [, M" [! W: h
tfNetwork 是opencv的深度网络分类器,场景scene and pixmaps 用来显示,变量 videoStopped 作为一个标记去停止视频. 我们的代码如下所示. 6 j7 I, r4 J( v1 E( [. I/ f # Q h9 A$ F; q' s#ifndef MAINWINDOW_H # g$ g5 ?9 i d* m#define MAINWINDOW_H* B4 `& S4 e9 Z7 ~8 ]& v" M
; X' {9 w, S% G& e p
#include <QMainWindow> 7 r8 B: P7 k' x, W#include <QMessageBox> # p; Y( L; D# O! `; p& I/ F#include <QDebug>, h" k: ^7 @. Z6 d
#include <QFile> % j) E$ Y, b$ e1 J9 `. W& ~#include <QElapsedTimer> P# n$ t: I' V% H9 w2 Z: Z7 I1 ~
#include <QGraphicsScene>; A5 o' f# [. \ I) G& P( x
#include <QGraphicsPixmapItem># C8 l0 M" [8 @+ E0 n3 H5 j
#include <QCloseEvent> ; }5 ~6 } L- B#include <QFileDialog> 5 f1 U& l; H9 H5 K; J* u; ?1 C+ c#include <opencv2/opencv.hpp> 7 K0 [8 G: f- I6 o& \1 m1 I0 | - e4 y( U; D' N! e' |namespace Ui {! G% L" N4 Z$ T# I# P2 B( j% b
class MainWindow; 4 J: J- S4 Y m3 h7 t} ( u4 i7 O" G, ^! i. i * S! S8 s/ U; U$ b9 U" A3 J0 xclass MainWindow : public QMainWindow; Z, N, `, ~/ ^# U4 H( J; E
{ ' l2 Y7 b, S: |' `: r. L Q_OBJECT* R7 h' }! N6 u& V ?
( r" o- B* y$ K, U2 T! s, Y. S3 C3 {public: % h9 }$ E# M7 f( a% l$ r explicit MainWindow(QWidget *parent = 0); ( f) P! j# S7 V2 o- { F% R ~MainWindow();: @3 h. l4 S- m: q( C
! e& b0 t: ^/ K- D7 V) r6 U
void closeEvent(QCloseEvent *event);9 I- H8 r K% w9 \9 q
& Q3 y. K. y b' j
private slots: * ~! y9 s9 ]9 Y, k; a5 M void on_startBtn_pressed(); ) L3 `$ ]# |4 ]/ l' W ! t9 n1 L* p# R; p6 A& S% ?# x6 P* J void on_browseVideoBtn_pressed();! Q6 o1 J9 b5 C! j& W
; `& L+ t! [9 s7 i6 k
void on_pbBrowseBtn_pressed();2 ^" r8 O. x$ y+ K
2 s9 |/ ?! x, Q, c5 B$ `+ M
void on_pbtxtBrowseBtn_pressed(); 2 U. y4 G% C/ ~ l $ c" y* i) T. g0 g6 P! s void on_classesBrowseBtn_pressed(); / W% u$ T0 R6 X. G- V9 L& W0 |# j; W
private:) ?- `- _' u1 H4 P; l6 A0 z1 P
Ui::MainWindow *ui; v& E6 n- Y6 o( I% d9 `$ O$ R
; K' i7 A+ j3 E9 ^2 u cv::dnn::Net tfNetwork; & G0 e7 Q( w0 O3 I r7 c 8 d+ x+ x: F* Y# g D4 c2 j QGraphicsScene scene; ; |2 B5 K: G/ W* E0 L5 H QGraphicsPixmapItem pixmap; 3 a" i' |3 ^/ w/ F S8 }( Z4 }# p5 J" |
bool videoStopped;3 l" |8 k: T% }3 k
% P( G1 n; k6 Z, n
}; # B! n, L& o4 j* l; v# ~2 V' u0 x! q) r. b4 g1 u
#endif // MAINWINDOW_H ; S% e$ B$ u4 a! b1. A. t9 a0 e, L# j
2. r; }2 ~: @- T: {3 g
39 j% Z/ z& o I/ x5 ^: N O
4 & k& v' A* X5 X& q; I% X* N- v5 $ F& F0 S5 P) a' \6 * `( i4 K/ n+ {1 a7 ( ^& m. p v( C% V( ]+ C- R8 ( X' B0 R1 r1 g9 ; \6 f+ |& ^; S I2 z+ Y% {10 7 G# _9 \" V) [/ \11 ( Y. t7 f, m& ^ ^* A+ A+ B& z$ B12 L; l8 i$ o9 K
13 5 r7 O. }: U+ G% v: m3 M6 I145 _3 A# ~: _7 r9 u
152 n z; Y8 `- z1 A! Q- Z
16' c+ {7 r; i- j l$ @: G% T) v# Y
17 9 U% `1 b+ A$ B/ e3 k& u x: L! O4 q18 ; E/ T& u' w3 z7 v19& P& S( k9 |% h9 F( L$ e& j
20 ) m+ J; ]. e' H* J, S; k( e: Q6 f218 s$ X) @8 Z& ]
22 4 n+ F$ y- f5 d9 E23( R6 i* b2 V3 r# Z
24 . ^) U7 ^- O# q' j( _5 U8 M6 ^; K& s25% v: S, ~; D3 [: F& P+ p
26, I0 T# u5 m. H ?: N
27 # t. H" ]5 Y; w& A28 # z2 `9 `. X# t4 m29( o- J6 V# J; Q4 X5 r4 f- I
30$ y, U9 M7 R5 J1 u
31 0 S: Z( W& I8 ?/ [32 6 H* p- g8 T! v5 Z) `" P33" O" W- D y; ^& f
345 m* R- |4 k; C3 Y- N: ]: S
357 D& I2 s4 e3 d! |9 u6 C& z: T
36 * ~9 ^9 S, _& m* j9 V, u37. ]4 G4 A9 W }# M- H# b8 Z
382 {: ]1 z# |5 e2 G% z
399 L3 m( B' z5 t' Q6 Z7 y
40: H/ h: t5 o8 H9 E% Z; M6 W( }
41 $ D/ u0 ~6 J4 r. M. w/ Y42$ C) F/ r% W8 I7 F$ K
43+ o/ _! r6 [- ~7 ?( S
44' n; C2 X8 I8 |6 \* }
45 ! ]3 B. I+ |5 a/ [$ {46 # q9 q* _% V; H" d# a3 P472 m( j6 ]% R$ m5 t
484 v! E: F& ~3 G% I2 L. s) {) ]+ f
49+ g1 p+ N( q' F
50 + h0 O3 h# ?+ f9 |519 a( q3 q4 C* W+ q5 a+ j
52 & E: O# P8 V3 ?* F: X“mainwindow.cpp” 包含一些方法去处理用户狡猾,并且加载TensorFlow 模型和配置,并且执行探测,也就是做推理,首先,我们需要把预先训练的模型加载到网络里面。7 c5 {3 O% l9 c, X+ ~
tfNetwork = readNetFromTensorflow(ui->pbFileEdit->text().toStdString(), ui->pbtxtFileEdit->text().toStdString()); 9 }! M6 @% \4 g; z( |! C. C # C5 W: e5 `# |* WpbFileEdit and pbtxtFileEdit 在代码里面是qt的 Line Edit widgets, 是所需要文件的地址路径,下一步,装载一个视频文件或者打开计算机的相机,使用两个 radio buttons, 让用户切换相机和视频文件,代码如下:1 C ~5 \+ E" B3 m8 { ?
9 w4 l0 B+ u1 O5 t
VideoCapture video; 1 v% L2 T+ _) M5 [/ \7 \if (ui->cameraRadio->isChecked())7 X5 A- L5 O& S5 N) X
video.open(ui->cameraSpin->value());+ d& L3 g4 m# h$ [
else* d" H7 c2 A4 }# r
video.open(ui->videoEdit->text().toStdString());6 C* }/ V' B& B! w' B( K* P: c
1 ) S& q7 P2 F# S1 m: S2- V, \2 k- `4 ~4 X
3 : R( t6 n6 ?! T5 V4 . v. C7 `- s! S5( _3 \ |" O9 X& K% y7 H
cameraRadio 是一个 Radio Button, cameraSpin 是一个 Spin Box 并且 videoEdit 是一个 Line Edit widget. 下一步我们需要循环读取视频帧并且处理推理,一直到结束:6 m7 k/ V5 V; y) R0 C% X2 h
0 t% m! Z9 F$ a0 n
Mat image;& H, s, i" h0 i: |
* ?$ Z# G8 r" Y5 G5 l9 Uwhile (!videoStopped && video.isOpened())/ P! [( k) _1 x R% ~
{ ' C7 z4 u5 J5 k% h3 i video >> image; , g8 x8 D' g2 Q1 X ! ~& P% i" x; w; g/ d // Detect objects ... * d: e7 j8 g, L& @ m 1 _. X: i7 z' _! C* }! { qApp->processEvents(); ; F/ ~2 R, s) o7 o}1 f& I* H) s- r7 Y4 u* ?- l# E
10 I* ?# V( v8 \
2( {) Q5 Y7 d) S
35 w$ J0 q4 i: o9 _% b
41 e7 K& K& a! V3 A% H* o; P/ a1 A
5 5 J( R; z" p3 x3 c) O( T$ `! t6 4 s" h% `' ?% l& c- Q/ Q7' j7 U# t! w. Q s
83 g, n+ n n1 ~) u$ R
9 9 D, O, ^4 @7 I( q, q$ z& \4 T10 - [4 N( H/ `- j+ f% O有很多方法可以去同步GUI和处理任务,我们把部分代码放入到QThread里面,探测部分如下所示:首先创建一个BLOB 兼容的Tensorflow 模型! a( r4 R1 o \! Z" ]
5 Q/ Y; k0 I/ H. y1 W" o
blobFromImage(InputArray image, 6 p! U Z0 k; P& t+ `! o3 @+ g4 j
double scalefactor=1.0, 3 p7 Q* F& Y) g" U3 f const Size& size = Size(), 3 m/ w6 y l' f- |" e, ` const Scalar& mean = Scalar(), 0 |" A6 y" a: ~: S! q1 P$ g% w
bool swapRB = false, " _8 A; i' u& j) y' k# f) M bool crop = false,& p+ z4 Q; x/ F
int ddepth = CV_32F)* M$ s4 y" D" ]' Q$ {) N0 Y0 E
12 x2 i1 d3 |% G- o/ h. w2 b0 D+ s
2 4 A! m! r3 U6 E1 J$ y/ W6 ]; u3 2 s5 @# i5 A' C6 R1 e) r e% i4( n0 {9 }! p' Z. J7 C9 r/ Q
5+ C4 h4 r% U- i0 w4 ?" H
6 ! L# v* Q8 @2 ~. i2 I9 ^7 c4 z3 E3 T( Z( W& h( T
下面是解释:+ c d* ^7 a1 i/ J& Q( z. O; [
0、image ,输入图片7 i8 x4 Y' {* X0 |6 o
1 、mean:需要将图片整体减去的平均值,如果我们需要对RGB图片的三个通道分别减去不同的值,那么可以使用3组平均值,如果只使用一组,那么就默认对三个通道减去一样的值。减去平均值(mean):为了消除同一场景下不同光照的图片,对我们最终的分类或者神经网络的影响,我们常常对图片的R、G、B通道的像素求一个平均值,然后将每个像素值减去我们的平均值,这样就可以得到像素之间的相对值,就可以排除光照的影响。 9 `) m: A2 H2 _3 ?5 i! `' e8 [- T6 q
2、scalefactor:当我们将图片减去平均值之后,还可以对剩下的像素值进行一定的尺度缩放,它的默认值是1,如果希望减去平均像素之后的值,全部缩小一半,那么可以将scalefactor设为1/2。- j+ n7 \, {. \6 t; s