云相册管家系统——毕业设计

目录

 

一、背景概述

二、效果演示

三、系统分析与设计

3.1,资源及工具

3.2,功能需求

3.3,系统部署

3.4,数据库设计

四、部分代码实现(C++部分比较多,放最后)

4.0,nginx 配置

4.1,GO

4.2,qml(gitee链接)

4.3,python(gitee链接)

4.4,C++(gitee链接)

4.4.1 主程序的框架

4.4.2 TensorFlow SSD MobileNet模型训练与使用

4.4.3 YOLOV3模型使用

4.4.4 简单的图片形态学处理

4.4.5 重复图像检测的算法实现(PSNR和SSIM)

4.4.6 base64编码

4.4.7 二进制数据和Mat数据之间的转化

4.4.8 跨域问题

4.4.9 C++与websocket传输图片

结语

源代码


一、背景概述

注:这里只是对毕业设计做个简单的介绍,包括一些源代码的解释,以及用到的技术(详细的UML图在gitee上有)

这次做的内容主要是为了模拟一款软件的开发(从设计到运维),主要使用C++实现整套系统(http服务,使用opencv-dnn模块+yolov3+TensorFlow模型迁移学习)的主要框架,同时结合了简单python(支付宝,腾讯云图像处理api),go(留言系统),qml(留言系统的简单显示)。

二、效果演示

图2-1:登陆注册页面
图2-2:用户相册页面
图2-3:上传图片(websocket)
图2-4:智能分类(yolov3使用)
图2-5:回收站
图2-6:联系作者(后台:Go+后台显示:qml)
图2-7:支付宝支付页面
图2-8:高级功能
图2-9:文字识别(腾讯云api)

 

图2-10:人数检测(TensorFlow模型训练)

三、系统分析与设计

由于用例图、活动图、顺序图等都是在初稿时设计的,有些变动,暂不提供,有需要请私聊联系

3.1,资源及工具

  • 开发语言:h5(css),js,c++,golang,python,qml
  • 开发环境:腾讯云服务器(ubuntu),Qt5.12,cmake,pc(ubuntu),webstorm,pycharm,virtualparadigm CE
  • 涉及到的框架:TensorFlow,yolov3模型,opencv,nginx

3.2,功能需求

  • 用户登陆、注册、修改个人信息
  • 图片上传、下载(格式转化)、删除、恢复
  • 图片自动分类(yolov3)
  • 场景文字识别
  • 纯文本检测(由于识别率比较低,后台代码实现了但未在网页中使用,普通的二值化,膨胀,腐蚀等形态学操作,确定区域,再通过tesseract识别文字)
  • 场景文字识别(CSER,opencv官网提供了原理和代码)
  •  腾讯云api(文字识别与检测)
  •  TensorFlow模型训练及导出使用(人头数量检测,之前是用于人流量识别)

3.3,系统部署

在云相册管家的部署中,网页、支付系统、数据库、HTTP服务端这几个程序都是可以独立于任何一台服务器的。由于服务器数量的受限,未能将每个程序放到不同的服务器做到分布式管理。在整个系统中,只使用了一台云服务器,使用了一台云服务器,将将所有的所有的程序放在此服务器中,再程序放在此服务器中,再使用使用nginx做反向代理做反向代理。用户可以通过浏览器访问https://www.zjyxxn.cn访问云相册管家系统。

模块与模块之间(就不同语言之间)采用socket通讯方式(TCP)
 

3.1:系统部署

3.4,数据库设计

整个系统使用到的数据库部分比较少,只有三个表:user,imagepath,contact。分别是存放用户信息,用户图片的信息,以及留言表。
    

3.2:ER图

四、部分代码实现(C++部分比较多,放最后)

4.0,nginx 配置

https://blog.csdn.net/qq_37960222/article/details/97789245

4.1,GO

    golang主要实现的是留言系统部分,实现内容比较简单,就是数据库的连接,以及留言系统页面的请求与相应,使用go的原因主要是因为如果再把这个模块整合到C++中(用C++实现),当某天主系统崩溃的时候,留言系统还能继续发挥作用(和前端的数据交互是单独的,不依赖任何模块的)

#主函数
package main

import (
	"fmt"
	"net/http"
)
var mdb *ContactDB
type MyHandler struct {}
func (mh MyHandler) ServeHTTP(w http.ResponseWriter,r *http.Request){
	if r.URL.Path =="/contact/"{
		_name := r.FormValue("name")  //FormValue(),如果name存在,也是获取第一个值【查源码就知道了】
		_email := r.PostFormValue("email") // PostFormValue()也是同理,name存在是,也是获取第一个值
		_message := r.Form.Get("message")
		print(_email,_name,_message)

		isRecv := mdb.insertData(_name,_email,_message)
		if isRecv==true {
			w.Write([]byte("send success"))
		}else {
			w.Write([]byte("send failed"))
		}
		return //这里的return是要加的,不然下面的代码也会执行了
	}
	if r.URL.Path == "/world"{
		w.Write([]byte("world page"))
		return
	}
	// 可以继续写自己的路由匹配规则
	print(r.URL.Path)
}
func makeHandler(handlers ...http.HandlerFunc)http.HandlerFunc{
	return func(w http.ResponseWriter, r *http.Request) {
		for _,hander := range handlers{
			hander(w,r)
		}

	}
}

func main() {
	mdb = &ContactDB{}
	mdb.initDB()
	http.Handle("/contact/", MyHandler{})
	if err := http.ListenAndServe(":7997",nil); err!=nil{
		fmt.Println("start http server fail:", err)
	}
}
#数据库连接
package main

import (
	"database/sql"
	"encoding/json"
	"fmt"
	_ "github.com/go-sql-driver/mysql"
	"time"
)
const (//需要改相应的配置
	USERNAME = "root"
	PASSWORD = ""
	NETWORK  = "tcp"
	SERVER   = "localhost"
	PORT     = 3306
	DATABASE = "graduation"
)
type Message struct {
	Name sql.NullString  `db:"name"`  //由于在mysql的users表中name没有设置为NOT NULL,所以name可能为null,在查询过程中会返回nil,如果是string类型则无法接收nil,但sql.NullString则可以接收nil值
	Email sql.NullString `db:"email"`
	Text sql.NullString `db:"text"`
	id int `db:"id"`
}

type ContactDB struct {
	DB *sql.DB
}


func (mydb *ContactDB)initDB() {
	dsn := fmt.Sprintf("%s:%s@%s(%s:%d)/%s",USERNAME,PASSWORD,NETWORK,SERVER,PORT,DATABASE)
	var err error
	mydb.DB,err = sql.Open("mysql",dsn)
	if err != nil{
		fmt.Printf("Open mysql failed,err:%v\n",err)
	}
	mydb.DB.SetConnMaxLifetime(100*time.Second)  //最大连接周期,超过时间的连接就close
	mydb.DB.SetMaxOpenConns(100)//设置最大连接数
	mydb.DB.SetMaxIdleConns(16) //设置闲置连接数
}
func (mydb *ContactDB)queryOne(name string){
	msg := new(Message)
	row := mydb.DB.QueryRow("select * from contact where name=?",name)
	//row.scan中的字段必须是按照数据库存入字段的顺序,否则报错
	if err :=row.Scan(&msg.Name,&msg.Email,&msg.Text,&msg.id); err != nil{
		fmt.Printf("scan failed, err:%v",err)
		return
	}

	fmt.Println(*msg)
}
//查询多行
func (mydb *ContactDB)queryMulti(){
	msg := new(Message)
	rows, err := mydb.DB.Query("select * from contact")
	defer func() {
		if rows != nil {
			rows.Close()
		}
	}()
	if err != nil {
		fmt.Printf("Query failed,err:%v", err)
		return
	}
	for rows.Next() {
		err = rows.Scan(&msg.Name, &msg.Email, &msg.Text,&msg.id)
		if err != nil {
			fmt.Printf("Scan failed,err:%v", err)
			return
		}
		fmt.Println(*msg)
		data,_ := json.Marshal(msg)
		fmt.Print(string(data))

	}

}

//插入数据
func (mydb *ContactDB)insertData(_name string,_email string, _text string) bool{
	result,err := mydb.DB.Exec("insert INTO contact(name,email,text) values(?,?,?)",_name,_email,_text)
	if err != nil{
		fmt.Printf("Insert failed,err:%v",err)
		return false
	}
	lastInsertID,err := result.LastInsertId()
	if err != nil {
		fmt.Printf("Get lastInsertID failed,err:%v",err)
		return false
	}
	fmt.Println("LastInsertID:",lastInsertID)
	rowsaffected,err := result.RowsAffected()
	if err != nil {
		fmt.Printf("Get RowsAffected failed,err:%v",err)
		return false
	}
	fmt.Println("RowsAffected:",rowsaffected)
	return true
}

//更新数据
func (mydb *ContactDB)updateDataName(_name string,_id int){
	result,err := mydb.DB.Exec("UPDATE users set name=? where id=?",_name,_id)
	if err != nil{
		fmt.Printf("Insert failed,err:%v",err)
		return
	}
	rowsaffected,err := result.RowsAffected()
	if err != nil {
		fmt.Printf("Get RowsAffected failed,err:%v",err)
		return
	}
	fmt.Println("RowsAffected:",rowsaffected)
}
func (mydb *ContactDB)updateDataEmail(_email string,_id int){
	result,err := mydb.DB.Exec("UPDATE users set email=? where id=?",_email,_id)
	if err != nil{
		fmt.Printf("Insert failed,err:%v",err)
		return
	}
	rowsaffected,err := result.RowsAffected()
	if err != nil {
		fmt.Printf("Get RowsAffected failed,err:%v",err)
		return
	}
	fmt.Println("RowsAffected:",rowsaffected)
}
func (mydb *ContactDB)updateDataText(_text string,_id int){
	result,err := mydb.DB.Exec("UPDATE users set text=? where id=?",_text,_id)
	if err != nil{
		fmt.Printf("Insert failed,err:%v",err)
		return
	}
	rowsaffected,err := result.RowsAffected()
	if err != nil {
		fmt.Printf("Get RowsAffected failed,err:%v",err)
		return
	}
	fmt.Println("RowsAffected:",rowsaffected)
}
//删除数据
func (mydb *ContactDB)deleteData(_id int){
	result,err := mydb.DB.Exec("delete from users where id=?",_id)
	if err != nil{
		fmt.Printf("Insert failed,err:%v",err)
		return
	}
	rowsaffected,err := result.RowsAffected()
	if err != nil {
		fmt.Printf("Get RowsAffected failed,err:%v",err)
		return
	}
	fmt.Println("RowsAffected:",rowsaffected)
}

 

4.2,qml(gitee链接

qml主要是对留言系统的数据做个简单的界面显示。

TableView{
        id: contactTable
        anchors.fill: parent
        function updateMsg(){
            msgModel.clear()
            var url="https://www.zjyxxn.cn/contact/contactDB"
            HttpHandler.get(url,
                            function(result){
                                var _ListVal = HttpHandler.parseResult(result)
                                for(var i=0;i<_ListVal.length;i++){
                                    console.debug(_ListVal[i][3])
                                    msgModel.append({"_id": _ListVal[i][0], "_name":_ListVal[i][1],"_email":_ListVal[i][2],"_text":_ListVal[i][3]})
                                }
                            },function (){              //失败后的回调函数
                                console.log("error")
                            }
                         );


                console.debug("update")
            }

            //TableViewColumn 描述表格的每一列
            TableViewColumn{role: "_id"; title: "Id"; width: 30; elideMode: Text.ElideRight;}
            TableViewColumn{role: "_name"; title: "Name"; width: 100;}
            TableViewColumn{role: "_email"; title: "Email"; width: 140;}
            TableViewColumn{role: "_text"; title: "Text"; width: 100;}

            rowDelegate: Rectangle{
                height: 20
                color: styleData.selected?"blue":"white"
            }

            itemDelegate:Text{//设置每个单元格的字体样式
                text: styleData.value
                height: 25
                color: styleData.selected? "red" : styleData.textColor
                elide: styleData.elideMode
                font.pixelSize: 20
            }

            headerDelegate :Rectangle{//设置表头的样式
                implicitWidth: 20
                implicitHeight: 48
                gradient: styleData.pressed ? contactTable.pressG :
                       (styleData.containsMouse ? contactTable.hoverG : contactTable.nomalG)
                border.width: 1
                border.color: "gray"
                Text{
                    anchors.verticalCenter: parent.verticalCenter
                    anchors.left: parent.left
                    anchors.leftMargin: 4
                    anchors.right: parent.right
                    anchors.rightMargin: 4
                    text: styleData.value
                    color: styleData.pressed ? "red" : "blue"
                    font.bold: true
                }
            }
            model:ListModel{
                id: msgModel

            }


            focus: true
        }
// GET
function get(url, success, failure)
{
    var xhr = new XMLHttpRequest;
    xhr.open("GET", url);
    xhr.onreadystatechange = function() {
        handleResponse(xhr, success, failure);
    }
    xhr.send();
}

// POST
function post(url, arg, success, failure)
{
    var xhr = new XMLHttpRequest;
    xhr.open("POST", url);
    xhr.setRequestHeader("Content-Length", arg.length);
    xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded;");  //用POST的时候一定要有这句
    xhr.onreadystatechange = function() {
        handleResponse(xhr, success, failure);
    }
    xhr.send(arg);
}



// 处理返回值
function handleResponse(xhr, success, failure){
    if (xhr.readyState == XMLHttpRequest.DONE) {
        if (xhr.status ==  200){
            if (success != null && success != undefined)
            {
                var result = xhr.responseText;
                try{
                    success(result, JSON.parse(result));
                }catch(e){
                    success(result, {});
                }
            }
        }
        else{
            if (failure != null && failure != undefined)
                failure(xhr.responseText, xhr.status);
        }
    }
}

function parseResult(result){
    var rtnVal=new Array()
    result=result.slice(1,result.length)
    var len=result.length
    var resList=0
    while(resList<len){
        if(result.charAt(resList++)==="{"){
            var perVal=""
            while(result.charAt(resList)!=="}"){
                perVal+=result.charAt(resList++)
            }
            resList++//因为{}和{}中间还有个“,”
            rtnVal.push(parsePerVale(perVal))
        }
    }
//    console.debug(rtnVal[0])
    return rtnVal
}

function parsePerVale(perVal){
    var len=perVal.length
    var beg=0
    var commaNum=0
    var perRtnVal=new Array()
    while(commaNum<4){
        var w=""
        while(beg<len){
            if(perVal.charAt(beg)===","){
                beg++
                break
            }else{
                w+=perVal.charAt(beg++)
            }
        }
        if(commaNum === 3){
            while(beg<len){
                w+=perVal.charAt(beg++)
            }
        }

        perRtnVal.push(w)
        commaNum++
    }
    return perRtnVal
}

4.3,python(gitee链接

python主要实现的是腾讯云文字识别api的连接(虽然也用opencv实现了文字识别,但是识别效果很差,因此在系统中还是使用了api)以及支付宝的支付(使用的沙箱环境,真正支付的话需要有企业证书才可以)。具体操作看“支付宝开放平台开发助手”

腾讯云api使用的是base64编码。在整个工程中,C++程序将接受到的图片转成base64编码的格式,并通过socket将图片的base64值传给python程序,python程序通过调用腾讯云api(官方的教程中有使用说明,并给了相应的代码)将处理好的图片和文本传递给C++程序,再将结果传给用户。

4.4,C++(gitee链接

需要修改CMakeLists.txt动态库的位置,修改zjynamespace里面相应的数据库账号密码,objectdetection.cpp中模型的位置,在CMakeLists.txt目录下创建build目录&&cd build,运行cmake ..  后运行 make,运行./bsTest就可以运行C++主程序

4.4.1 主程序的框架

//httpserver.h




#include "headers.h"
#include "imagebasetool.h"
#ifndef HTTPSERVER_H
#define HTTPSERVER_H


class HttpServer
{
    using ReqHandler = zjy::ReqHandler<UserManageInterface>;
public:
    HttpServer();
    ~HttpServer();
    void init(const std::string &port);
    bool start();
    bool close();
    void addHandler(const std::string &url, ReqHandler req_handler);
    void removeHandler(const std::string & url);
    static void addSession(mg_connection* conn);
    static std::unordered_map<mg_connection*,std::string> format_websocket_session;
    static std::unordered_set<mg_connection*> s_websocket_session_set;
    static std::string s_web_dir;//web root
    static mg_serve_http_opts s_server_option;//web server opetion
    static std::unordered_map<std::string, ReqHandler> s_handler_map;
    static DBPool* _db;
    static std::unordered_map<std::string, UserManageInterface*> s_session;

private:
    static void OnHttpWebsocketEvent(mg_connection *connection, int event_type, void *event_data);
    static void HandleHttpEvent(mg_connection *connection, http_message *http_req);
    static int isWebSocket(const mg_connection* connection);
    static void HandleWebsocketMessage(mg_connection* connection, int event_type, websocket_message* ws_msg, std::string filename);
    static void SendWebsocketMsg(mg_connection* connection, std::string msg,bool setClose);
    static void SendWebsocketMsg_binary(mg_connection* connection, std::string msg,bool setClose);
    static void BroadcastWebsocketMsg(std::string msg);
    /*
     * @std::pair<mg_connection*,std::string> the fist value means which client connected,
     *  second velue storage the client's image type
    */

    std::string m_port;    // 端口
    mg_mgr m_mgr;          // 连接管理器
    static int num;

public:
    //this para is used to test
    //static UserManageInterface* _umm;

};

#endif // HTTPSERVER_H

//httpserver.cpp

#include <exception>
#include "httpserver.h"
#include "log.h"

//user upload's picture name,used to slite suffix

extern std::string uploadformat;
std::string uploadformat{};
HttpServer::HttpServer()
{
}

HttpServer::~HttpServer()
{

}

static bool route_check(http_message *http_msg, char *route_prefix)
{
    if (mg_vcmp(&http_msg->uri, route_prefix) == 0)
        return true;
    else
        return false;

    // TODO: 还可以判断 GET, POST, PUT, DELTE等方法
    //mg_vcmp(&http_msg->method, "GET");
    //mg_vcmp(&http_msg->method, "POST");
    //mg_vcmp(&http_msg->method, "PUT");
    //mg_vcmp(&http_msg->method, "DELETE");
}

void HttpServer::init(const std::string &port)
{
    m_port = port;
    s_server_option.enable_directory_listing = "yes";
    s_server_option.document_root = s_web_dir.c_str();

    //开启 CORS,本项只针对主页加载有效
    s_server_option.extra_headers = "Access-Control-Allow-Origin: *";
}

bool HttpServer::start()
{
    mg_mgr_init(&m_mgr, nullptr);
    mg_connection *connection = mg_bind(&m_mgr, m_port.c_str(), HttpServer::OnHttpWebsocketEvent);
    if (connection == nullptr)
        return false;
    // for both http and websocket
    mg_set_protocol_http_websocket(connection);

    printf("starting http server at port: %s\n", m_port.c_str());
    // loop
    while (true){
        mg_mgr_poll(&m_mgr, 500); // ms
    }
}

bool HttpServer::close()
{
    mg_mgr_free(&m_mgr);
    return true;
}

void HttpServer::addHandler(const std::string &url, HttpServer::ReqHandler req_handler)
{
    if (s_handler_map.find(url) != s_handler_map.end()){
        LOG::ErrorLog(__FILE__, __LINE__, "url exist");
        return;
    }
    s_handler_map.insert(std::make_pair(url, req_handler));
}

void HttpServer::removeHandler(const std::string &url)
{
    auto it = s_handler_map.find(url);
    if (it != s_handler_map.end())
        s_handler_map.erase(it);
}

void HttpServer::addSession(mg_connection *conn)
{
    s_websocket_session_set.insert(conn);
}

void HttpServer::OnHttpWebsocketEvent(mg_connection *connection, int event_type, void *event_data)
{
    // distinguish http and websocket
    if (event_type == MG_EV_HTTP_REQUEST) {
        http_message *http_req = static_cast<http_message *>(event_data);
        HandleHttpEvent(connection, http_req);
    }
    else if (event_type == MG_EV_WEBSOCKET_HANDSHAKE_DONE ||
             event_type == MG_EV_WEBSOCKET_FRAME ||
             event_type == MG_EV_CLOSE) {
        websocket_message *ws_message = static_cast<struct websocket_message *>(event_data);
        HandleWebsocketMessage(connection, event_type, ws_message,"");
    }
}

void HttpServer::HandleHttpEvent(mg_connection *connection, http_message *http_req)
{
    std::string req_str = std::string(http_req->message.p, http_req->message.len);
    printf("got request: %s\n", req_str.c_str());
    // 先过滤是否已注册的函数回调
    std::string url = std::string(http_req->uri.p, http_req->uri.len);
    std::string body = std::string(http_req->body.p, http_req->body.len);


    try {
        struct mg_str *cookie_header;
        cookie_header=mg_get_http_header(http_req, "Cookie");    //获取Cookie 整条
        if(cookie_header == nullptr)
            throw "";
        std::string getCookie = std::string(cookie_header->p,cookie_header->len);
        auto pos = getCookie.find("phone=");
        if(pos!=std::string::npos){
           
        }
    } catch (...){
    }

    auto _exist = HttpServer::s_session.find(phone);
    if(HttpServer::s_session.end() == _exist){
    }else{
    }
    auto it = s_handler_map.find(url);
    if (it != s_handler_map.end())
    {

        ReqHandler handle_func = it->second;
        handle_func(connection, http_req, umm/*HttpServer::_umm*/);
    }else{
        // other request
        std::string rootpage="/";
        if (route_check(http_req, const_cast<char*>(rootpage.c_str()))) // index page
            mg_serve_http(connection, http_req, s_server_option);
        else {

        }
    }


}

int HttpServer::isWebSocket(const mg_connection *connection)
{
    return connection->flags & MG_F_IS_WEBSOCKET;
}
/*websock always used to send image from client to server*/
void HttpServer::HandleWebsocketMessage(mg_connection *connection, int event_type, websocket_message *ws_msg, std::string /*filename*/)
{  
    if( event_type == MG_EV_WEBSOCKET_HANDSHAKE_DONE ){

    } else if (event_type == MG_EV_WEBSOCKET_FRAME) {
        mg_str received_msg = {(char*)ws_msg->data, ws_msg->size};
    } else if (event_type == MG_EV_CLOSE) {
        if (isWebSocket(connection))
        {
            LOG::SuccessLog(__FILE__, __LINE__, "client websocket closed\n");
            // move session
            if (s_websocket_session_set.find(connection) != s_websocket_session_set.end())
                s_websocket_session_set.erase(connection);
        }
    }
}

void HttpServer::SendWebsocketMsg(mg_connection *connection, std::string msg, bool setClose=false)
{
    mg_send_websocket_frame(connection, WEBSOCKET_OP_TEXT, msg.c_str(), strlen(msg.c_str()));
    if(setClose==true){
        mg_send_websocket_frame(connection,WEBSOCKET_OP_CLOSE,nullptr,0);
    }

}

void HttpServer::SendWebsocketMsg_binary(mg_connection *connection, std::string msg, bool setClose=false)
{
    mg_send_websocket_frame(connection, WEBSOCKET_OP_BINARY, msg.c_str(), strlen(msg.c_str()));
    if(setClose==true){
        mg_send_websocket_frame(connection,WEBSOCKET_OP_CLOSE,nullptr,0);
    }

}

void HttpServer::BroadcastWebsocketMsg(std::string msg)
{
    for (mg_connection *connection : s_websocket_session_set)
        mg_send_websocket_frame(connection, WEBSOCKET_OP_TEXT, msg.c_str(), strlen(msg.c_str()));
}

4.4.2 TensorFlow SSD MobileNet模型训练与使用

见往期博客:https://blog.csdn.net/qq_37960222/article/details/104460663

4.4.3 YOLOV3模型使用

using namespace cv::dnn;
std::list<string> zjyObjectDetection::image_detection(cv::Mat &pic)
{
    using namespace zjy::detection;
    Net net = readNetFromDarknet(yolo_tiny_cfg, yolo_tiny_model);
    net.setPreferableBackend(DNN_BACKEND_OPENCV);
    net.setPreferableTarget(DNN_TARGET_CPU);
    std::vector<String> outNames = net.getUnconnectedOutLayersNames();
//    for (int i = 0; i < outNames.size(); i++) {
//        printf("output layer name : %s\n", outNames[i].c_str());
//    }

    vector<string> classNamesVec;
    ifstream classNamesFile("/home/ubuntu/bs_test/src/object_detection_classes_yolov3.txt");
    if (classNamesFile.is_open())
    {
        string className = "";
        while (std::getline(classNamesFile, className))
            classNamesVec.push_back(className);
    }

    // 加载图像

    Mat inputBlob = blobFromImage(pic, 1 / 255.F, Size(416, 416), Scalar(), true, false);
    net.setInput(inputBlob);

    // 检测
    std::vector<Mat> outs;
    net.forward(outs, outNames);
    vector<double> layersTimings;
    double freq = getTickFrequency() / 1000;
    double time = net.getPerfProfile(layersTimings) / freq;
    ostringstream ss;
    ss << "detection time: " << time << " ms";
    putText(pic, ss.str(), Point(20, 20), 0, 0.5, Scalar(0, 0, 255));
    vector<Rect> boxes;
    vector<int> classIds;
    vector<float> confidences;
    for (size_t i = 0; i<outs.size(); ++i)
    {
        float* data = (float*)outs[i].data;
        for (int j = 0; j < outs[i].rows; ++j, data += outs[i].cols)
        {
            Mat scores = outs[i].row(j).colRange(5, outs[i].cols);
            Point classIdPoint;
            double confidence;
            minMaxLoc(scores, 0, &confidence, 0, &classIdPoint);
            if (confidence > 0.5)
            {
                int centerX = (int)(data[0] * pic.cols);
                int centerY = (int)(data[1] * pic.rows);
                int width = (int)(data[2] * pic.cols);
                int height = (int)(data[3] * pic.rows);
                int left = centerX - width / 2;
                int top = centerY - height / 2;

                classIds.push_back(classIdPoint.x);
                confidences.push_back((float)confidence);
                boxes.push_back(Rect(left, top, width, height));
            }
        }
    }

    std::set<std::string> st;
    std::list<std::string> ls;
    vector<int> indices;
    NMSBoxes(boxes, confidences, 0.5, 0.2, indices);
    cout<<indices.size()<<endl;
    for (size_t i = 0; i < indices.size(); ++i)
    {

        int idx = indices[i];
        Rect box = boxes[idx];
        String className = classNamesVec[classIds[idx]];
        putText(pic, className.c_str(), box.tl(), FONT_HERSHEY_SIMPLEX, 1.0, Scalar(255, 0, 0), 2, 8);
        rectangle(pic, box, Scalar(0, 0, 255), 2, 8, 0);
        st.insert(className);
    }
    for_each(st.begin(),st.end(),[&ls](std::string s){ std::cout<<s<<std::endl; ls.push_back(s); });
    return ls;
}

4.4.4 简单的图片形态学处理

由于之前写没上传github/gitee,目前只有在objectdetecion.cpp98行开始的/*text detection2*/有部分代码,tesseract的代码在家里另外的测试项目中没上传,等回去记得的话再上传。

4.4.5 重复图像检测的算法实现(PSNR和SSIM)

double ImageBaseTool::getPSNR(const cv::Mat &src, const cv::Mat &dst)
{
    cv::Mat s1;
    absdiff(src, dst, s1);       // |src - dst|
    s1.convertTo(s1, CV_32F);  // cannot make a square on 8 bits
    s1 = s1.mul(s1);           // |src - dst|^2   

    cv::Scalar s = sum(s1);         // sum elements per channel

    double sse = s.val[0] + s.val[1] + s.val[2]; // sum channels

    if( sse <= 1e-10) // for small values return zero
        return 0;
    else
    {
        double  mse =sse /(double)(src.channels() * src.total());
        double psnr = 10.0*log10((255*255)/mse);
        return psnr;
    }
}

cv::Scalar ImageBaseTool::getMSSIM(const cv::Mat &src, const cv::Mat &dst)
{
    const double C1 = 6.5025, C2 = 58.5225;
    int d = CV_32F;
    cv::Mat I1, I2;
    src.convertTo(I1, d);
    dst.convertTo(I2, d);
    cv::Mat I2_2 = I2.mul(I2);
    cv::Mat I1_2 = I1.mul(I1);
    cv::Mat I1_I2 = I1.mul(I2);
    cv::Mat mu1, mu2;
    GaussianBlur(I1, mu1, cv::Size(11, 11), 1.5);
    GaussianBlur(I2, mu2, cv::Size(11, 11), 1.5);
    cv::Mat mu1_2 = mu1.mul(mu1);
    cv::Mat mu2_2 = mu2.mul(mu2);
    cv::Mat mu1_mu2 = mu1.mul(mu2);
    cv::Mat sigma1_2, sigma2_2, sigma12;
    GaussianBlur(I1_2, sigma1_2, cv::Size(11, 11), 1.5);
    sigma1_2 -= mu1_2;
    GaussianBlur(I2_2, sigma2_2, cv::Size(11, 11), 1.5);
    sigma2_2 -= mu2_2;
    GaussianBlur(I1_I2, sigma12, cv::Size(11, 11), 1.5);
    sigma12 -= mu1_mu2;
    cv::Mat t1, t2, t3;
    t1 = 2 * mu1_mu2 + C1;
    t2 = 2 * sigma12 + C2;
    t3 = t1.mul(t2);
    t1 = mu1_2 + mu2_2 + C1;
    t2 = sigma1_2 + sigma2_2 + C2;
    t1 = t1.mul(t2);
    cv::Mat ssim_map;
    divide(t3, t1, ssim_map);
    cv::Scalar mssim = mean( ssim_map );
    return mssim;
}

4.4.6 base64编码

static const std::string base64_chars{"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"};
std::string ImageBaseTool::base64_encode(const char *bytes_to_encode, unsigned int in_len)
{
    std::string ret;
    int i = 0;
    int j = 0;
    unsigned char char_array_3[3];
    unsigned char char_array_4[4];

    while (in_len--) {
        char_array_3[i++] = *(bytes_to_encode++);
        if (i == 3) {
            char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
            char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
            char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
            char_array_4[3] = char_array_3[2] & 0x3f;

            for(i = 0; (i <4) ; i++)
                ret += base64_chars[char_array_4[i]];
            i = 0;
        }
    }

    if (i)
    {
        for(j = i; j < 3; j++)
            char_array_3[j] = '\0';

        char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
        char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
        char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
        char_array_4[3] = char_array_3[2] & 0x3f;

        for (j = 0; (j < i + 1); j++)
            ret += base64_chars[char_array_4[j]];

        while((i++ < 3))
            ret += '=';
    }

    return ret;
}

std::string ImageBaseTool::base64_decode(const std::string &encoded_string)
{
    int in_len = encoded_string.size();
    int i = 0;
    int j = 0;
    int in_ = 0;
    unsigned char char_array_4[4], char_array_3[3];
    std::string ret;

    while (in_len-- && ( encoded_string[in_] != '=') && is_base64(encoded_string[in_])) {
        char_array_4[i++] = encoded_string[in_]; in_++;
        if (i ==4) {
            for (i = 0; i <4; i++)
                char_array_4[i] = base64_chars.find(char_array_4[i]);

            char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
            char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
            char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];

            for (i = 0; (i < 3); i++)
                ret += char_array_3[i];
            i = 0;
        }
    }

    if (i) {
        for (j = i; j <4; j++)
            char_array_4[j] = 0;

        for (j = 0; j <4; j++)
            char_array_4[j] = base64_chars.find(char_array_4[j]);

        char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
        char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
        char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];

        for (j = 0; (j < i - 1); j++) ret += char_array_3[j];
    }

    return ret;
}

4.4.7 二进制数据和Mat数据之间的转化

cv::Mat ImageBaseTool::binaryToMat(std::string data)
{
    cv::Mat pic;
    std::vector<uchar> picData;
    std::string::iterator beg=data.begin();
    while (beg!=data.end()) {
        picData.push_back(*beg);
        beg++;
    }
    pic = cv::imdecode(picData,cv::IMREAD_COLOR);
    return pic;
}

std::string ImageBaseTool::matToBinary(cv::Mat &src, std::string type)
{
    std::string str;
    std::vector<unsigned char> buff;
    cv::imencode(type, src, buff);
    str.resize(buff.size());
    memcpy(&str[0], buff.data(), buff.size());
    return str;
}

4.4.8 跨域问题

本地跨域问题(即使用的是localhost):https://blog.csdn.net/qq_37960222/article/details/104658105

互联网中跨域问题(使用域名):与使用localhost 的区别在于端口需不需要写

//type: content-type    Lenth:message length   content:what message you want to send to html
    static std::string header(std::string type, std::string Lenth){
        std::string header{"HTTP/1.1 200 OK\r\nContent-Type: "+type+"\nCache-Control: no-cache\nContent-Length: "+Lenth+"\n"
                      "Access-Control-Allow-Origin: https://www.zjyxxn.cn\nAccess-Control-Allow-Credentials: true\n"
                      "Access-Control-Allow-Methods:GET,POST,PUT,POST\nAccess-Control-Allow-Headers:x-requested-with,content-type\n"};
        return header;
    }
    static std::string sendMsg(std::string type, std::string &content, bool cookie = false, std::string cookieValue=""){
        std::string all{};
        all += header(type, std::to_string(content.length()));
        if (cookie == true) {
            all += "Set-Cookie:phone=" + cookieValue + "; domain=www.zjyxxn.cn; path=/\n";//domain=http:://localhost:443;
        }
        all += "\n";
        all += content;
        all += "\r\n";
        return all;
    }

4.4.9 C++与websocket传输图片

在使用websocket的时候,如果nginx使用的443端口,在new的时候要用“wss://域名”,否则用"ws://域名"

<script type="text/javascript">
      document.addEventListener('dragover', function(e){
         e.stopPropagation();
         e.preventDefault(); // 这里是为了取消浏览器自带的拖拽事件
      }, false);
      document.addEventListener('drop', function(e){
         e.stopPropagation();
         e.preventDefault();
         var file = e.dataTransfer.files[0]; // file就是需要传输的文件
      }, false);

      window.onload = function WebSocketTest() {
          userName()
         var ws = new WebSocket("wss://www.zjyxxn.cn/api/");
         if ("WebSocket" in window) {
            alert("您的浏览器支持 WebSocket!");

            // 打开一个 web socket
            ws.onopen = function () {
               var imgFile = document.getElementById("img11");
               //  var fr = new FileReader();
               imgFile.onchange = function(){
                  var reader = new FileReader();
                  var img= this.files[0];
                  reader.readAsArrayBuffer(img);
                  alert("数据发送中...");
                  reader.onload = function loaded(evt) {
                     var blob = evt.target.result;
                     //发送json数据,包括用户id,文件名,处理类型
                     u_phone= document.cookie;
                     u_phone = u_phone.slice(6);
                     var json;
                     json="{\"phone\":\""+u_phone+"\",\"img_name\":\""+img.name+"\",\"type\":\"0\"}"
                     ws.send(json);
                     //发送二进制表示的文件
                     ws.send(blob);
                     ws.onmessage = function (evt) {
                        var received_msg = evt.data;
                        alert(received_msg);
                     };

                     ws.onclose = function () {
                        // 关闭 websocket
                        alert("连接已关闭...");
                     };
                  }
               }
            };


         } else {
            // 浏览器不支持 WebSocket
            alert("您的浏览器不支持 WebSocket!");
         }
      };

   </script>

结语

这次毕业设计做的相对来说还是比较水的。原本想着要从头开始造轮子,可是到最后,感觉没有做到自己想要的那个结果。主程序中还存在不少的bug还没有解决,如果用其他语言编写主程序,相对会容易很多,而且bug也会少很多(至少在后期编写go的时候,这种反差感很强烈)。

由于程序基本上是半年前完成的,关于在代码中的一些问题记得不是很清楚,博客内容也相对来说比较简单,更详细的还是要去读源代码。

源代码

gitee地址:https://gitee.com/zengjingyu/graduation_design

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值