基于OpenCV技术的汽车牌照的识别

author: RoWe98(RexRowe)

OpenCV是一个基于BSD许可(开源)发行的跨平台计算机视觉库,可以运行在Linux、Windows、Android和Mac OS操作系统上。它轻量级而且高效——由一系列
C 函数和少量 C++ 类构成,同时提供了Python、Ruby、MATLAB等语言的接口,实现了图像处理和计算机视觉方面的很多通用算法。
车牌识别技术要求能够将运动中的汽车牌照从复杂背景中提取并识别出来,通过车牌提取、图像预处理、特征提取、车牌字符识别等技术,识别车辆牌号、颜色等信息,目前最新的技术水平为字母和数字的识别率可达到99.7%,汉字的识别率可达到99%。
车牌识别技术结合电子不停车收费系统(ETC)识别车辆,过往车辆通过道口时无须停车,即能够实现车辆身份自动识别、自动收费。在车场管理中,为提高出入口车辆通行效率,车牌识别针对无需收停车费的车辆(如月卡车、内部免费通行车辆),建设无人值守的快速通道,免取卡、不停车的出入体验,正改变出入停车场的管理模式。

设计平台及工具

1.Visual Studio 2017

2.OpenCV2.4.9

3.C++

设计原理

车牌自动识别是一项利用车辆的动态视频或静态图像进行牌照号码、牌照颜色自动识别的模式识别技术。其硬件基础一般包括触发设备(监测车辆是否进入视野)、摄像设备、照明设备、图像采集设备、识别车牌号码的处理机(如计算机)等,其软件核心包括车牌定位算法、车牌字符分割算法和光学字符识别算法等。某些车牌识别系统还具有通过视频图像判断是否有车的功能称之为视频车辆检测。一个完整的车牌识别系统应包括车辆检测、图像采集、车牌识别等几部分(如图1所示)。当车辆检测部分检测到车辆到达时触发图像采集单元,采集当前的视频图像。车牌识别单元对图像进行处理,定位出牌照位置,再将牌照中的字符分割出来进行识别,然后组成牌照号码输出。

主要框架:

avatar

图像的预处理

车牌定位的第一步为图像预处理。为了方便计算,系统通常将获取的图片灰度化。将彩色图像转化成为灰度图像的过程就称为图像的灰度化处理。彩色图像中R、G、B三个分量的值决定了具体的像素点。一个像素点可以有上千万种颜色。而灰度图像是一种彩色图像,但是它的特点在于R、G、B三个分量具体的值是一致的。灰度图中每个像素点的变化区间是0到255,由于方便计算,所以在实际工程处理中会先将各种格式的图像转变成灰度图像。在保留图像轮廓和特征的基础上,灰度图仍然能够反映整幅图像轮廓和纹理。在Opencv里面有实现图像灰度化的接口。调用OpenCV中的cvSmooth函数进行中值滤波处理,以去除细小毛刺。

图像二值化

局部自适应二值化是针对灰度图像中的每一个像素逐点进行阈值计算,它的阈值是由像素周围点局部灰度特性和像素灰度值来确定的。局部阈值法是逐个计算图像的每个像素灰度级,保存了图像的细节信息,非均匀光照条件等情况虽然影响整个图像的灰度分布缺不影响局部的图像性质,但也存在缺点和问题,相比全局阈值法来说,它的计算时间较长,但适用于多变的环境。
设图像在像素点(x,y)处的灰度值为f(x,y),考虑以像素点(x,y)为中心的(2w+1)*(2w+1)窗口(w为窗口宽度),则局部自适应二值化算法可以描述如下:

a.计算图像中各点(x,y)的阈值w(x,y)

W(x,y)=0.5*(max f(x+m,y+n)+min f(x+m,y+n))

b.如果f(x,y)>w(x,y),则二值化结果为1,代表字符区域的目标点;否则二值化结果为0,代表背景区域的目标点。

车牌位置的定位

车牌细定位的目的是为下一步字符的分割做,就是要进一步去掉车牌冗余的部分。在一幅经过适当二值化处理 含有车牌的图像中,车牌区域具有以下三个基本特征:
列在一个不大的区域内密集包含有多个字符;
车牌字符与车牌底色形成强烈对比;
车牌区域大小相对固定,区域长度和宽度成固定比例。
根据以上特征,车牌区域所在行相邻像素之间0 到1和1到0 的的变化会很频繁,变化总数会大于一个临界值,这可以作为寻找车牌区域的一个依据。 因此根据跳变次数与设定的阈值比较,就可以确定出车牌的水平区域。
由于车牌一般悬挂在车辆下部,所以采用从上到下,从左到右的方式对图像进行扫描。车牌的字符部分由7个字符数与两个竖直边框组成,则车牌区域内任一行的跳变次数至少为(7+2)2=18次。从图像的底部开始向顶部进行扫描,则第一组连续数行且每行的跳变次数都大于跳变阈值,同时满足连续行数大于某个阈值。
在车牌的水平区域中,最高行与最低行的差值即为车牌在图像中的高度。我国车牌区域为矩形,宽高比约为3.14,取3.14
H作为车牌的宽度。在水平区域内选择任意一行,用L长的窗口由左至右移动,统计窗口中相邻像素0,1的跳变次数并存入数组中。若窗口移动到车牌的垂直区域时,窗口内的跳变次数应该最大。因此在数组中找到最大值,其对应的区域即为车牌的垂直区域。

车牌字符的识别

车牌字符识别是字符识别的重要组成部分。车牌字符识别的最终目的就是将图像中的车牌字符转化成文本字符,车牌字符的识别属于印刷体识别范畴。
字符识别的基本思想是匹配判别。抽取待识别字符特征按照字符识别的原理和预先存储在计算机中的标准字符模式表达形式的集合逐一进行匹配,找出最接近输入字符模式的表达形式,该表达形式对应的字就是识别结果。

字符识别的原理如下:

avatar

根据我国的车牌牌照标准,车牌第一位字符一般为汉字,车牌第二位英文大写字母,第三位至第七位为英文大写字母或数字。考虑到神经网络对小类别字符集有较高的识别率,因此在车牌字符识别系统中,分别设计三个神经网络:汉字网络、字母网络、字母数字网络 实现对字符的分类识别。

avatar

程序代码

1.OpencvCar.h 车牌识别类

程序

#pragma once
#pragma comment (lib, "cxcore.lib")
#pragma comment (lib, "cv.lib")
#pragma comment (lib, "ml.lib")
#pragma comment (lib, "cvaux.lib")
#pragma comment (lib, "highgui.lib")
#pragma comment (lib, "cvcam.lib")
#include "cv.h"
#include "highgui.h"
#include "iostream"
#include "string"
using namespace std;
#define DEBUGTEST
#define POINT_X  18                                           // 水平方向不重点                        
#define POINT_Y  2                                            // 垂直方向不重点                            
#define WITH_X 0.1                                            // 水平方向车牌在域     
#define WITH_Y 0.2                                            // 垂直方向车牌在域     
#define HIGH_WITH_CAR   4.8   //(440/85)               // 小车的宽高比  440/140    
#define CHARACTER 15
#define TEMPLETENUM  43
class OpencvCar
{
public:
    OpencvCar();
    int CodeRecognize(IplImage *imgTest, int num, int char_num); //字符识别
    int SegmentPlate();                                //字符分割
    int PlateAreaSearch(IplImage *pImg_Image, IplImage *src);        //车牌定位
    void Threshold(IplImage *Image, IplImage *Image_O);    //二值化
    int AdaptiveThreshold(int t, IplImage *Image);        //自适应阈值法
protected:
    IplImage *src;                                // 原始图片
    IplImage *pImgCanny;                      //二值化的图
    IplImage *pImgResize;                     //归一化的车牌区域灰度图
    IplImage *pImgCharOne;                // 字符图片
    IplImage *pImgCharTwo;
    IplImage *pImgCharThree;
    IplImage *pImgCharFour;
    IplImage *pImgCharFive;
    IplImage *pImgCharSix;
    IplImage *pImgCharSeven;
};

2.OpencvCar.cpp 车牌识别类实现程序

程序

#include "OpencvCar.h"

// 车牌字符模板特征值
const int Num_Templete[TEMPLETENUM][CHARACTER] =
{
    { 16, 19, 10, 12, 10, 10, 15, 18, 110, 3, 2, 2, 3, 3, 3 },     //0
    { 9, 11, 10, 10, 10, 10, 9, 10, 79, 2, 2, 2, 0, 2, 12 },       //1
    { 18, 19, 3, 18, 10, 10, 23, 22, 123, 4, 2, 2, 7, 6, 8 },      //2
    { 19, 21, 11, 14, 4, 20, 18, 22, 129, 2, 2, 4, 6, 6, 7 },      //3
    { 2, 18, 11, 22, 20, 21, 11, 18, 123, 2, 4, 2, 6, 7, 5 },      //4
    { 23, 19, 20, 12, 9, 20, 18, 22, 143, 2, 4, 4, 6, 6, 6 },      //5
    { 6, 13, 17, 8, 15, 20, 18, 20, 117, 2, 2, 4, 5, 7, 6 },       //6
    { 21, 21, 0, 20, 8, 12, 9, 11, 102, 2, 2, 2, 2, 8, 15 },       //7
    { 17, 18, 18, 19, 14, 20, 17, 20, 143, 4, 2, 4, 6, 6, 6 },     //8
    { 16, 18, 15, 21, 7, 19, 13, 7, 116, 3, 2, 2, 6, 6, 5 },       //9
    { 10, 10, 16, 16, 20, 20, 18, 19, 129, 2, 4, 2, 8, 3, 6 },     //A
    { 24, 20, 20, 19, 22, 22, 24, 20, 171, 4, 8, 4, 6, 6, 6 },     //B
    { 18, 19, 20, 4, 20, 8, 17, 21, 127, 3, 2, 4, 4, 4, 4 },       //C
    { 23, 19, 11, 20, 12, 20, 22, 21, 148, 3, 3, 3, 4, 4, 4 },     //D
    { 23, 19, 21, 9, 22, 8, 23, 23, 148, 2, 2, 2, 6, 6, 6 },       //E
    { 25, 17, 20, 9, 22, 8, 19, 0, 120, 2, 2, 2, 4, 4, 4 },        //F
    { 17, 18, 22, 14, 12, 24, 18, 21, 146, 4, 7, 4, 4, 6, 6 },     //G
    { 14, 20, 18, 22, 17, 22, 16, 20, 149, 4, 1, 4, 2, 2, 2 },     //H
    { 0, 17, 0, 20, 3, 20, 18, 22, 100, 2, 2, 4, 2, 2, 2 },        //J
    { 19, 20, 26, 10, 20, 20, 20, 22, 157, 4, 4, 4, 3, 5, 11 },    //K
    { 20, 0, 20, 0, 20, 0, 25, 20, 105, 2, 2, 2, 2, 2, 2 },        //L
    { 20, 10, 27, 17, 20, 10, 22, 14, 140, 1, 3, 3, 4, 1, 5 },     //M
    { 21, 12, 25, 17, 26, 12, 18, 18, 149, 3, 5, 3, 5, 5, 6 },     //N 
    { 23, 19, 18, 20, 21, 8, 22, 0, 131, 3, 3, 2, 4, 4, 4 },       //P
    { 18, 19, 20, 10, 26, 15, 18, 21, 147, 3, 3, 4, 5, 7, 5 },     //Q
    { 26, 19, 21, 18, 21, 17, 20, 21, 163, 4, 3, 4, 4, 6, 5 },     //R
    { 18, 18, 18, 10, 8, 17, 17, 22, 128, 4, 3, 4, 6, 6, 6 },      //S
    { 22, 18, 10, 10, 10, 10, 10, 10, 100, 2, 2, 2, 33, 2, 2 },    //T
    { 18, 12, 20, 10, 20, 10, 19, 21, 130, 3, 3, 3, 2, 2, 2 },     //U
    { 20, 19, 20, 20, 15, 14, 9, 10, 127, 4, 4, 2, 9, 1, 8 },      //V
    { 21, 25, 26, 28, 16, 16, 21, 19, 172, 6, 2, 4, 13, 0, 7 },    //W
    { 21, 21, 13, 13, 12, 11, 22, 21, 134, 4, 2, 4, 8, 0, 10 },    //X
    { 21, 20, 10, 11, 10, 10, 10, 11, 103, 3, 2, 2, 5, 2, 6 },     //Y
    { 21, 23, 5, 15, 15, 5, 24, 20, 128, 2, 2, 2, 8, 8, 7 },       //Z
    { 13, 14, 10, 10, 10, 10, 13, 13, 93, 2, 2, 2, 29, 2, 29 },    //I
    { 20, 20, 13, 20, 19, 12, 17, 20, 141, 3, 3, 4, 4, 4, 4 },     //O          //36
    { 14, 15, 17, 17, 16, 10, 25, 24, 138, 0, 2, 4, 12, 8, 9 },    //云        //37
    { 17, 20, 17, 12, 33, 28, 23, 20, 170, 3, 4, 7, 13, 6, 4 },    //苏
    { 21, 21, 23, 24, 24, 25, 31, 27, 196, 0, 9, 6, 8, 6, 7 },     //京
    { 19, 27, 20, 34, 19, 36, 24, 37, 216, 4, 4, 7, 13, 28, 3 },   //湘
    { 17, 14, 23, 27, 36, 40, 26, 27, 210, 4, 13, 4, 16, 14, 14 }, //鲁
    { 24, 24, 32, 38, 34, 32, 17, 22, 223, 9, 6, 10, 11, 12, 9 }, // 粤
    { 22, 20, 33, 37, 25, 24, 24, 25, 210, 13, 3, 6, 12, 8, 7 }     //蒙
};

//车牌字符
const char *PlateCode[TEMPLETENUM] =
{
    "0", "1", "2", "3", "4",
    "5", "6", "7", "8", "9",
    "A", "B", "C", "D", "E",
    "F", "G", "H", "J", "K",
    "L", "M", "N", "P", "Q",
    "R", "S", "T", "U", "V",
    "W", "X", "Y", "Z", "I", "O",
    "云", "苏", "京", "湘", "鲁", "粤", "蒙"
};
const char *G_PlateChar[7] = { "没", "有", "找", "到", "车", "牌", "!" }; // 车牌号
OpencvCar::OpencvCar()
{
    //初始化  变量
    IplImage *src = NULL;      //原始图片                        
    IplImage *pImgCanny = NULL;  //二值化的图                    
    IplImage *pImgResize = NULL;   //归一化的车牌区域灰度图                  
    IplImage *pImgCharOne = NULL;   //字符图片             
    IplImage *pImgCharTwo = NULL;
    IplImage *pImgCharThree = NULL;
    IplImage *pImgCharFour = NULL;
    IplImage *pImgCharFive = NULL;
    IplImage *pImgCharSix = NULL;
    IplImage *pImgCharSeven = NULL;
}
/* ----------------------自适应阈值法---------------------------------//
//--input: t:中心阈值
//--                Image: 图片指针
//-- output:  return   自适应均值的阈值
//-------------------------------------------------------------------------*/
int OpencvCar::AdaptiveThreshold(int t, IplImage *Image)
{
    int t1 = 0, t2 = 0, tnew = 0, i = 0, j = 0;
    int Allt1 = 0, Allt2 = 0, accountt1 = 0, accountt2 = 0;//Allt1 Allt2 保存两部分的和 
    for (j = 0; j < Image->height; j++) //根据现有T,将图像分为两部分,分别求两部分的平均值t1、t2
    {
        for (i = 0; i < Image->width; i++)
        {
            if (CV_IMAGE_ELEM(Image, uchar, j, i) < t)
            {
                Allt1 += CV_IMAGE_ELEM(Image, uchar, j, i);
                accountt1++;
            }
            else
            {
                Allt2 += CV_IMAGE_ELEM(Image, uchar, j, i);
                accountt2++;
            }
        }
    }
    t1 = Allt1 / accountt1;
    t2 = Allt2 / accountt2;
    tnew = 0.5*(t1 + t2);
    if (tnew == t) //若t1、t2的平均值和t相等,则阈值确定
        return tnew;
    else
        AdaptiveThreshold(tnew, Image); //不等则以t1、t2的平均值为新阈值迭代
}
/* -----------------------二值化-----------------------------------------//
// --Input:
//              IplImage *Image: 图片指针
//             int AdaptiveThreshold(int t,IplImage *Image)  //自适应阈值法
//-- Output:
//              IplImage *Image_O  二值化后的图片
//--  Description:
//              采用Canny边缘检测二值化
//-------------------------------------------------------------------------*/
void OpencvCar::Threshold(IplImage *Image, IplImage *Image_O)
{
    //得到图片的最大灰度值和最小灰度值
    int thresMax = 0, thresMin = 255, i = 0, j = 0, t = 0;
    for (j = 0; j < Image->height; j++)
        for (i = 0; i < Image->width; i++)
        {
            if (CV_IMAGE_ELEM(Image, uchar, j, i) > thresMax) //像素值 大于 255  
                thresMax = CV_IMAGE_ELEM(Image, uchar, j, i);  //把元素值赋给 thresMax
            else if (CV_IMAGE_ELEM(Image, uchar, j, i) < thresMin) //如果小于 0   
                thresMin = CV_IMAGE_ELEM(Image, uchar, j, i); //则 改变thresMin
        }
    //int T=(thresMax+thresMin)*0.5; //灰度的最大值和最小值的平均
    cvCanny(Image, Image_O, AdaptiveThreshold((thresMax + thresMin)*0.5, Image), thresMax*0.7, 3);
    //小阈值用来控制边缘连接  大阈值用来控制强边缘的初始化分割  cvCanny只接受单通道的输入
}
//车牌区域检测
/***************************************************
INPUT  : pImg_Image   二值化后的图像
src         原始图像
OUTPUT :  pImgResize  归一化后的车牌灰度图像
Description :
输出归一化图片大小 40*20
归一化的区域为在在原始图片(src) 归一化之后,在再二值化后 图
定位方法:  水平分割,垂直分割,归一化。
***********************************************************/
int OpencvCar::PlateAreaSearch(IplImage *pImg_Image, IplImage *src)
{
    if (pImg_Image == NULL) { return 0; } // 检测是否有值

    IplImage* imgTest = 0;
    int i = 0, j = 0, k = 0, m = 0;
    bool flag = 0;
    int plate_n = 0, plate_s = 0, plate_e = 0, plate_w = 0;  //关于车牌的一些变量

    int *num_h = new int[max(pImg_Image->width, pImg_Image->height)];
    if (num_h == NULL)
    {
        cout << "memory exhausted" << endl;
        //MessageBox("memory exhausted!");
        return 0;
        //    exit(1);    
    }  // end if 

    for (i = 0; i < pImg_Image->width; i++) { num_h[i] = 0; }  // 初始化 分配的空间

    imgTest = cvCreateImage(cvSize(pImg_Image->width, pImg_Image->height), IPL_DEPTH_8U, 1);
    cvCopy(pImg_Image, imgTest);

    //-- 水平 轮廓细化
    for (j = 0; j < imgTest->height; j++)
    {
        for (i = 0; i < imgTest->width - 1; i++)
        {
            CV_IMAGE_ELEM(imgTest, uchar, j, i) = CV_IMAGE_ELEM(imgTest, uchar, j, i + 1) - CV_IMAGE_ELEM(imgTest, uchar, j, i);
            num_h[j] += CV_IMAGE_ELEM(imgTest, uchar, j, i) / 250;
        }
    }


    int temp_1 = 0;
    int temp_max = 0;
    int temp_i = 0;  //说明这里for 循环 是找出数据量最大的地方  20行  也即是 车牌区域
    for (j = 0; j < imgTest->height - 20; j++)
    {
        temp_1 = 0;
        for (i = 0; i < 20; i++)//此处for循环 是为了计算20行的总数据量  
            temp_1 += num_h[i + j];
        if (temp_1 >= temp_max)
        {
            temp_max = temp_1;
            temp_i = j;//记录20行的最大数据量的开始 行
        }
    }
    k = temp_i;//以下两个while 循环是为了找出 车牌的上下边界  当一行的数据量小于某个数值时 设定此为分界行
    while (((num_h[k + 1] > POINT_X) || (num_h[k + 2] > POINT_X) || (num_h[k] > POINT_X)) && k) k--;//找出行边界行
    plate_n = k + 1;//k+2; 
    k = temp_i + 10;
    while (((num_h[k - 1] > POINT_X) || (num_h[k - 2] > POINT_X) || (num_h[k] > POINT_X)) && (k < imgTest->height)) k++; //找出下边界行
    plate_s = k;//k-2; 

    // 没找到水平分割线,设置为默认值
    if (!(plate_n && plate_s  //行为负值 或者 上行大于下行  或者 车牌宽度大于 设定值 则水平分割失败
        && (plate_n < plate_s) && ((plate_s - plate_n)*HIGH_WITH_CAR < imgTest->width*(1 - WITH_X))))
    {
        //flag=1;
        cout << "水平分割失败" << endl;
        //MessageBox("水平分割失败!");
        return 0;
    }
    else//找到水平线  
    {
        int  max_count = 0;
        int  plate_length = (imgTest->width - (plate_s - plate_n)*HIGH_WITH_CAR);
        plate_w = imgTest->width*WITH_X - 1;//车牌宽度 默认

        //--垂直方向
        for (i = 0; i < imgTest->width; i++)
            for (j = 0; j < imgTest->height - 1; j++)//用的方法是 差分赋值法——我起的名字 应该是为了细化 
            {
                CV_IMAGE_ELEM(imgTest, uchar, j, i) = CV_IMAGE_ELEM(imgTest, uchar, j + 1, i) - CV_IMAGE_ELEM(imgTest, uchar, j, i);
            }
        //下面这一段代码 相当于 拿一个车牌大小的矩形区域 从左往右 滑动,什么时候圈住的数据量最大的时候
        // 就代表找到的车牌的左边界 此时 车牌左边界的横坐标是 k
        //这里 plate_length  有点难理解,它的值是 原图像的宽度减去车牌的宽度差值。
        for (k = 0; k < plate_length; k++)
        {
            for (i = 0; i < (int)((plate_s - plate_n)*HIGH_WITH_CAR); i++)
                for (j = plate_n; j < plate_s; j++)//两水平线之间 
                {
                    num_h[k] = num_h[k] + CV_IMAGE_ELEM(imgTest, uchar, j, (i + k)) / 250;
                }
            if (num_h[k] > max_count)
            {
                max_count = num_h[k];
                plate_w = k;
            }  // end if 

        }

        CvRect ROI_rect;                 //获得图片感兴趣区域
        ROI_rect.x = plate_w;
        ROI_rect.y = plate_n;
        ROI_rect.width = (plate_s - plate_n)*HIGH_WITH_CAR;
        ROI_rect.height = plate_s - plate_n;

        if ((ROI_rect.width + ROI_rect.x) > pImg_Image->width)
        {
            ROI_rect.width = pImg_Image->width - ROI_rect.x;
            cout << "垂直方向分割失败!";
            //MessageBox("垂直方向分割失败!");
            return 0;
        }
        else
        {
            IplImage *pImg8uROI = NULL;         //感兴趣的图片
            pImg8uROI = cvCreateImage(cvSize(ROI_rect.width, ROI_rect.height), src->depth, src->nChannels);
            IplImage *pImg8u11 = NULL;        //车牌区域灰度图
            pImg8u11 = cvCreateImage(cvSize(40 * HIGH_WITH_CAR, 40), pImg8uROI->depth, pImg8uROI->nChannels);

            cvSetImageROI(src, ROI_rect);//将 ROI_rect 设置为感兴趣区域
            cvCopy(src, pImg8uROI, NULL);//把感兴趣区域 复制到 pImg8uROI
            cvResetImageROI(src);   //重新设置感兴趣区域

            pImgResize = cvCreateImage(cvSize(40 * HIGH_WITH_CAR, 40), IPL_DEPTH_8U, 1);
            cvResize(pImg8uROI, pImg8u11, CV_INTER_LINEAR); //线性插值  归一化  把车牌变成统一大小

            cvCvtColor(pImg8u11, pImgResize, CV_RGB2GRAY);  //  转为灰度图      Y=0.299*R + 0.587*G + 0.114*B
            Threshold(pImgResize, pImgResize);             // 二值化

            cvReleaseImage(&pImg8uROI);
            cvReleaseImage(&pImg8u11);
            cvReleaseImage(&imgTest);

            cvNamedWindow("车牌");
            cvShowImage("车牌", pImgResize);
        }   // end if 
    }    // end if 

    // 释放内存
    delete[]num_h;
    num_h = NULL;
    return 1;
}
//* -----------------------字符分割-----------------------------------------//
// --Input: 
//              IplImage * pImgResize : 归一化的车牌区域灰度图   
//-- Output: 
//               IplImage *pImgCharOne              // 字符图片
//               IplImage *pImgCharTwo
//               IplImage *pImgCharThree
//               IplImage *pImgCharFour
//               IplImage *pImgCharFive
//               IplImage *pImgCharSix
//               IplImage *pImgCharSeven
//--  Description:  
//               利用垂直投影法和车牌的特征结合分割字符区域
//-------------------------------------------------------------------------*/  

int OpencvCar::SegmentPlate()
{
    if (pImgResize == NULL) { return 0; } // 没有切割成功,直接弹出

    int *num_h = new int[max(pImgResize->width, pImgResize->height)];     // 开辟空间  一般应该是 width 大小
    if (num_h == NULL)   // 
    {
        //cout<<"memory exhausted"< 0; j--)//从第二个字符的末端开始 往前找第一个和第二个字符起始位置
    {
        if ((num_h[j] < POINT_Y) && (num_h[j - 1] < POINT_Y))//只要有两个列的 17到24 行的值小于 2,
        {                                           //即找到第二个字符的开始位置
            letter[2] = j;      // find letter[2]  第二个字符的开始位置
            letter[1] = (j >= 23) ? j - 3 : letter[1];   //第一个字符的结束位置
            letter[0] = (j >= 23) ? j - 23 : letter[0];  //第一个字符的起始位置
            break;       //找到就退出循环
        }
    }

    j = 2;  flag = 0; flag1 = 0;//两个标记
    for (i = letter[4]; i < 40 * HIGH_WITH_CAR; i++)  //从第三个字符的开始位置算起
    {
        if ((num_h[i] > POINT_Y) && (num_h[i - 1] > POINT_Y) && !flag)
        {
            flag = 1;
            flag1 = 0;
            letter[2 * j] = i - 1; //这里 只记录字符的开始位置
            if (j == 6)  //判断 最后一个字符的结束位置 是否越界 超出界限,如果没有,则letter[13]=letter[12]+20
            {
                letter[2 * j + 1] = ((letter[2 * j] + 20) > 40 * HIGH_WITH_CAR - 1) ? 40 * HIGH_WITH_CAR - 1 : letter[2 * j] + 20;
                break;//退出 for循环
            }
        }
        else if ((num_h[i] < POINT_Y) && (num_h[i - 1] < POINT_Y) && !flag1 && flag)//如果是 空白区域  
        {
            flag = 0;
            flag1 = 1;
            letter[2 * j + 1] = i - 1;
            j++; //j自动加 1
        }                        //    1    
    }
    // 删除角点                              1   0   1
    for (i = 0; i < 40 * HIGH_WITH_CAR - 1; i++)
    {                                   //      1    删除角点  相当于拿一个半径为1 的圆 去圈 如果四周有两个是1  则自己设置为0   

        for (j = 0; j < 39; j++)  // 0-16 /40
        {

            if (CV_IMAGE_ELEM(pImgResize, uchar, j, i) && CV_IMAGE_ELEM(pImgResize, uchar, j, i + 1) && CV_IMAGE_ELEM(pImgResize, uchar, j + 1, i)) //  01
                CV_IMAGE_ELEM(pImgResize, uchar, j, i) = 0;                                                                                                                                                                                                                             //   1

            if (CV_IMAGE_ELEM(pImgResize, uchar, j, i) && CV_IMAGE_ELEM(pImgResize, uchar, j, i - 1) && CV_IMAGE_ELEM(pImgResize, uchar, j + 1, i))     //  10
                CV_IMAGE_ELEM(pImgResize, uchar, j, i) = 0;                                                                                                                                                                                                                               //    1                      

            if (CV_IMAGE_ELEM(pImgResize, uchar, j, i) && CV_IMAGE_ELEM(pImgResize, uchar, j, i - 1) && CV_IMAGE_ELEM(pImgResize, uchar, j - 1, i))         //   1      
                CV_IMAGE_ELEM(pImgResize, uchar, j, i) = 0;                                                                                    // 10
            if (CV_IMAGE_ELEM(pImgResize, uchar, j, i) && CV_IMAGE_ELEM(pImgResize, uchar, j, i + 1) && CV_IMAGE_ELEM(pImgResize, uchar, j - 1, i))           // 1      
                CV_IMAGE_ELEM(pImgResize, uchar, j, i) = 0;                                                                                 // 01
        }
    }

    // 分割出字符图片
    pImgCharOne = cvCreateImage(cvSize(20, 40), IPL_DEPTH_8U, 1);
    pImgCharTwo = cvCreateImage(cvSize(20, 40), IPL_DEPTH_8U, 1);
    pImgCharThree = cvCreateImage(cvSize(20, 40), IPL_DEPTH_8U, 1);
    pImgCharFour = cvCreateImage(cvSize(20, 40), IPL_DEPTH_8U, 1);
    pImgCharFive = cvCreateImage(cvSize(20, 40), IPL_DEPTH_8U, 1);
    pImgCharSix = cvCreateImage(cvSize(20, 40), IPL_DEPTH_8U, 1);
    pImgCharSeven = cvCreateImage(cvSize(20, 40), IPL_DEPTH_8U, 1);

    CvRect ROI_rect1;
    ROI_rect1.x = 0.5*(letter[1] + letter[0]) - 10; //
    ROI_rect1.y = 0;
    ROI_rect1.width = 20;
    ROI_rect1.height = 40;
    cvSetImageROI(pImgResize, ROI_rect1);
    cvCopy(pImgResize, pImgCharOne, NULL); //获取第1个字符
    cvResetImageROI(pImgResize);
    cvNamedWindow("第一个字符");
    cvShowImage("第一个字符", pImgCharOne);

    ROI_rect1.x = 0.5*(letter[3] + letter[2]) - 10;
    ROI_rect1.y = 0;
    ROI_rect1.width = 20;
    ROI_rect1.height = 40;
    cvSetImageROI(pImgResize, ROI_rect1);
    cvCopy(pImgResize, pImgCharTwo, NULL); //获取第2个字符
    cvResetImageROI(pImgResize);
    cvNamedWindow("第二个字符");
    cvShowImage("第二个字符", pImgCharTwo);

    ROI_rect1.x = 0.5*(letter[5] + letter[4]) - 10;
    ROI_rect1.y = 0;
    ROI_rect1.width = 20;
    ROI_rect1.height = 40;
    cvSetImageROI(pImgResize, ROI_rect1);
    cvCopy(pImgResize, pImgCharThree, NULL); //获取第3个字符
    cvResetImageROI(pImgResize);
    cvNamedWindow("第三个字符");
    cvShowImage("第三个字符", pImgCharThree);

    ROI_rect1.x = 0.5*(letter[7] + letter[6]) - 10;
    ROI_rect1.y = 0;
    ROI_rect1.width = 20;
    ROI_rect1.height = 40;
    cvSetImageROI(pImgResize, ROI_rect1);
    cvCopy(pImgResize, pImgCharFour, NULL); //获取第4个字符
    cvResetImageROI(pImgResize);
    cvNamedWindow("第四个字符");
    cvShowImage("第四个字符", pImgCharFour);

    ROI_rect1.x = 0.5*(letter[9] + letter[8]) - 10;
    ROI_rect1.y = 0;
    ROI_rect1.width = 20;
    ROI_rect1.height = 40;
    cvSetImageROI(pImgResize, ROI_rect1);
    cvCopy(pImgResize, pImgCharFive, NULL); //获取第5个字符
    cvResetImageROI(pImgResize);
    cvNamedWindow("第五个字符");
    cvShowImage("第五个字符", pImgCharFive);

    ROI_rect1.x = 0.5*(letter[11] + letter[10]) - 10;
    ROI_rect1.y = 0;
    ROI_rect1.width = 20;
    ROI_rect1.height = 40;
    cvSetImageROI(pImgResize, ROI_rect1);
    cvCopy(pImgResize, pImgCharSix, NULL); //获取第6个字符
    cvResetImageROI(pImgResize);
    cvNamedWindow("第六个字符");
    cvShowImage("第六个字符", pImgCharSix);

    ROI_rect1.x = 0.5*(letter[13] + letter[12]) - 10;
    ROI_rect1.y = 0;
    ROI_rect1.width = 20;
    ROI_rect1.height = 40;
    cvSetImageROI(pImgResize, ROI_rect1);
    cvCopy(pImgResize, pImgCharSeven, NULL); //获取第7个字符
    cvResetImageROI(pImgResize);
    cvNamedWindow("第七个字符");
    cvShowImage("第七个字符", pImgCharSeven);


    //字符识别
    CodeRecognize(pImgCharOne, 3, 0);
    CodeRecognize(pImgCharTwo, 1, 1);
    CodeRecognize(pImgCharThree, 2, 2);
    CodeRecognize(pImgCharFour, 2, 3);
    CodeRecognize(pImgCharFive, 0, 4);
    CodeRecognize(pImgCharSix, 0, 5);
    CodeRecognize(pImgCharSeven, 0, 6);

    //输出字符在控制台上
    string outFile = "";
    int carnum;
    for (carnum = 0; carnum < 7; carnum++)
    {
        outFile += G_PlateChar[carnum];
    }

    cout << "车牌为:";
    cout << outFile << endl;

    cvWaitKey();

    // 释放内存
    delete[]num_h;
    num_h = NULL;

}
//* -----------------------字符识别-----------------------------------------//
// --Input: 
//               IplImage *pImgCharOne              // 字符图片
//               IplImage *pImgCharTwo
//               IplImage *pImgCharThree
//               IplImage *pImgCharFour
//               IplImage *pImgCharFive
//               IplImage *pImgCharSix
//               IplImage *pImgCharSeven
//               int num                    // 数字字符汉字识别用,
//                                          // 0 数字 1英文 2英文和数字 3中文
//               int char_num               // 第char_num个车牌字符
//-- Output:   
//               char *G_G_PlateChar[7]      // 车牌号 
//--  Description:  
//               利用垂直投影法和车牌的特征结合分割字符区域
//-------------------------------------------------------------------------*/  

//说明:垂直投影 
//对于二值图像,水平方向的投影就是每行的非零像素值的个数,在这里就是1或者255,
//垂直投影就是每列图像数据中非零像素值的个数。
//车牌特征  就是 车牌一般大小  即长宽比例,还有每个字符之间的间隔 第二个和第三个字符之间间
//隔大些,车牌里有汉字、数字和字母,第一个是汉字,第二个是字母,后面是字母和数字,大体也就这么些特征
int OpencvCar::CodeRecognize(IplImage *imgTest, int num, int char_num)
{
    if (imgTest == NULL) { return 0; }

    int i = 0, j = 0, k = 0, t = 0;//循环变量
    int  char_start = 0, char_end = 0;//*PlateCode[TEMPLETENUM] 车牌字符里字母、数字、汉字起始位置
    int num_t[CHARACTER] = { 0 };
    switch (num)//这里这样分 可以提高效率,并且提高了识别率
    {
    case 0:  char_start = 0;         // 数字
        char_end = 9;
        break;
    case 1:  char_start = 10;        // 英文
        char_end = 35;
        break;
    case 2:  char_start = 0;       // 英文和数字
        char_end = 35;
        break;
    case 3:  char_start = 36;       // 中文
        char_end = TEMPLETENUM - 1;
        break;
    default: break;
    }
    // 提取前8个特征  前8个特征 可以说是固定位置的值  固定算法
    for (k = 0; k < 8; k++)
    {
        for (j = int(k / 2) * 10; j= 5)  //特征值 大于5 
            {
                if (abs(num_t[i] - Num_Templete[k][i]) <= 1)
                    matchnum += 2;
            }
            else if (num_t[i] == Num_Templete[k][i])
            {
                matchnum += 2;
            }
        }
        if (matchnum > matchnum_max)
        {
            matchnum_max = matchnum;  //保留最大的 匹配 
            matchcode = k;  //记录 识别的字符的 索引 
            //matchtempnum[j]=matchnum_min
        }
    }
    //识别输出  存放输出结果
    G_PlateChar[char_num] = PlateCode[matchcode]; //保存下该字符
    cout << "    匹配成功,匹配字符为: " << G_PlateChar[char_num] << endl;
}

3.CarReco.cpp 车牌识别主程序

程序

#include "OpencvCar.h"
using namespace std;
int main()
{
    IplImage *src;   //原始图片                        
    IplImage *pImgCanny; //二值化的图   
    IplImage *pImgResize;  //归一化的车牌区域灰度图                  
    IplImage *pImgCharOne;  //字符图片             
    IplImage *pImgCharTwo;
    IplImage *pImgCharThree;
    IplImage *pImgCharFour;
    IplImage *pImgCharFive;
    IplImage *pImgCharSix;
    IplImage *pImgCharSeven;
    //实例化OpencvCar类 
    OpencvCar cvCar;
    cout << "欢迎使用车牌识别系统" << endl;
    src = NULL;
    const char *filepath;
    filepath = "car3.jpg";  //等待识别的图片位置
    src = cvLoadImage(filepath); //读取图片
    cvNamedWindow("原图像");
    cvShowImage("原图像", src);
    pImgCanny = cvCreateImage(cvSize(src->width, src->height), IPL_DEPTH_8U, 1);     //  2值化后图片大小初始化
    cvCvtColor(src, pImgCanny, CV_RGB2GRAY);  //转化为灰度图  openCV函数 Y = 0.299*R + 0.587*G + 0.114*B   //  转为灰度图      Y=0.299*R + 0.587*G + 0.114*B
    cvSmooth(pImgCanny, pImgCanny, CV_GAUSSIAN, 3, 0, 0); 
    //二值化
    cvCar.Threshold(pImgCanny, pImgCanny);
    cvNamedWindow("二值化图像");
    cvShowImage("二值化图像", pImgCanny);
    //车牌定位
    cvCar.PlateAreaSearch(pImgCanny, src);
    //字符分割并识别
    cvCar.SegmentPlate();
    return 0;
}

#运行效果

效果

如何搭建一个图片服务器

author: RoWe98(RexRowe)

前言:

在分布式架构中,往往会有多个tomcat,然后你上传的图片只是在其中的某一个tomcat,你访问时是由集群的tomcat随机提供服务。当你访问的tomcat是有图片的那个时,图片能正常显示,如果恰巧是那个没有图片的tomcat时,图片就不能正常显示。这就完成了访问同一个图片,可能你刷新一次可以访问,再刷新一次图片就访问不到了。这时,我们就需要一个服务器用来专门存储图片,一般我们都用nginx。

简介:

1、nginx:

Nginx是一款轻量级的Web 服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器,并在一个BSD-like 协议下发行。其特点是占有内存少,并发能力强,事实上nginx的并发能力确实在同类型的网页服务器中表现较好,中国大陆使用nginx网站用户有:百度、京东、新浪、网易、腾讯、淘宝等。以上是百度百科的介绍,我们目前只需要知道nginx是一个服务器就行了,类似于tomcat的服务器,只不过我们把它用来保存图片。
2、vsftp:

VSFTP是一个基于GPL发布的类Unix系统上使用的FTP服务器软件,它有安全、高速、稳定等特点。我们暂且这样理解:vsftp就是用来传输文件的一个服务,在linux系统中开启vsftp服务,然后在windows中就可以通过linux系统的ip、vsftp服务的端口、vsftp的用户名及密码连接vsftp服务,然后就可以方便的把windows中东西上传到linux中,也可以把linux中的东西下载到windows中。
3、nginx+vsftp:

上面分别介绍了nginx和vsftp,那么这两个东西怎么组合起来用呢?怎么实现这个图片服务器呢?我们知道,tomcat安装好启动后,在浏览器输入localhost:8080,就会出现tomcat的欢迎页,nginx也一样。比如linux的ip是192.168.50.122,那么启动nginx后,在浏览器访问这个地址也会出现nginx的欢迎页,其实是因为它有个默认的访问页面,完整的地址应该是192.168.50.122/index.html,那么我们就可以根据这个,把它默认的访问页面改成我们上传的图片的保存路径,比如上传了一张pic.jpg图片到linux的/home/ftpuser/images中,如果我们把默认访问页面改成/home/ftpuser,那么在浏览器中输入192.168.50.122/images/pic.jpg,就可以访问到这张图片了。下面就来介绍nginx、vsftp的安装以及配置。

Nginx安装

依赖环境安装

yum install gcc

yum install pcre-devel

yum install zlib zlib-devel

yum install openssl openssl-devel

开启防火墙端口:

Centos7默认防火墙是firewalld,一下是相关命令

#service firewalld status; #查看防火墙状态
#service firewalld start;  或者 #systemctl start firewalld.service;
#开启防火墙# service firewalld stop;  或者 #systemctl stop firewalld.service;
#关闭防火墙# service firewalld restart;  或者 #systemctl restart firewalld.service;  
#重启防火墙# systemctl disable firewalld.service
#禁止防火墙开启自启# systemctl enable firewalld
#设置防火墙开机启动# yum remove firewalld
#卸载firewall# firewall-cmd --query-port=80/tcp  #查询端口号80 是否开启

先把nginx和vsftp相关端口开启,分别执行一下5条指令即可。

firewall-cmd --zone=public --add-port=80/tcp --permanent
firewall-cmd --zone=public --add-port=443/tcp --permanent
firewall-cmd --zone=public --add-port=22/tcp --permanent
firewall-cmd --zone=public --add-port=21/tcp --permanent
firewall-cmd --zone=public --add-port=30000-30999/tcp --permanent

①、下载:

wget -c https://nginx.org/download/nginx-1.10.1.tar.gz

②、解压:

tar -zxvf nginx-1.10.1.tar.gz
cd nginx-1.10.1

③、设置编译参数:

./configure \
--prefix=/usr/local/nginx \
--pid-path=/var/run/nginx/nginx.pid \
--lock-path=/var/lock/nginx.lock \
--error-log-path=/var/log/nginx/error.log \
--http-log-path=/var/log/nginx/access.log \
--with-http_gzip_static_module \
--http-client-body-temp-path=/var/temp/nginx/client \
--http-proxy-temp-path=/var/temp/nginx/proxy \
--http-fastcgi-temp-path=/var/temp/nginx/fastcgi \
--http-uwsgi-temp-path=/var/temp/nginx/uwsgi \
--http-scgi-temp-path=/var/temp/nginx/scgi

直接把这段代码贴到linux中执行就行了。

④、编译安装:

make && make install

⑤、启动:

cd /usr/local/nginx/sbin
./nginx

⑥、关闭nginx:
在刚才的sbin目录下执行:

./nginx -s stop

遇到的坑:

第一次启动nginx没问题,如果重启了一下虚拟机,再次到nginx的sbin目录下执行./nginx,出现下图所示的错误:

avatar

解决办法:
在run文件夹下创建一个nginx文件夹即可。

cd /var/run
mkdir nginx

然后再运行就可以了

#vsftp的安装:

1、安装:

yum -y install vsftpd

2、添加ftp用户:

useradd ftpuser

3、给ftp用户添加密码:

passwd ftpuser

输入两次密码后修改密码。
4、修改selinux:
①查看状态:

getsebool -a | grep ftp

执行这个命令可以看到

allow_ftpd_full_access --> off
ftp_home_dir --> off

这两个都off,执行如下命令设置为on:

[root@localhost ~]# setsebool -P ftpd_full_access on
[root@localhost ~]# setsebool -P ftp_home_dir on

再次执行getsebool -a | grep ftp看到那两个状态是on就行了。
5、关闭匿名访问:
执行

vim /etc/vsftpd/vsftpd.conf

avatar

还要在vsftp.conf文件最下面添加以下内容:

pasv_min_port=30000
pasv_max_port=30999

然后保存退出即可。

6、设置开机启动:

[root@localhost ~]# chkconfig vsftpd on

7、测试:
打开filezilla工具,输入虚拟机的ip,21端口,用户名和密码,点击快速连接,连接vsftp服务:

avatar

如图所示则连接成功。

#配置nginx为图片服务器:
按照以上步骤安装好nginx和vsftp后,还是不能访问上传的图片的,需要进行如下配置:
执行

vim  /usr/local/nginx/conf/ nginx.conf

命令,打开nginx的配置文件:
avatar

按道理这样就可以了,但是我访问却报错:
403 forbidden,最后发现是因为ftpuser文件夹没有可读权限,执行如下命令:

chmod -R 755 /home/ftpuser

再次访问即可成功!

avatar

avatar

END

利用Socket编程(TCP协议)完成聊天室

author: RoWe98(RexRowe)

利用Socket编程(TCP协议)完成聊天室

摘 要

使用基于TCP协议的Socket网络编程实现聊天室,TCP协议基于hex请求(Request)-响应(Response)模式,使用IO流实现数据的传输

二、设计平台

JAVA Socket编程

三、设计原理

TCP模型

avatar

C/S 模型
下图是基于TCP协议的客户端/服务器程序的一般流程:

avatar

服务器调用socket()、bind()、listen()完成初始化后,调用accept()阻塞等待,处于监听端口的状态,客户端调用socket()初始化后,调用connect()发出SYN段并阻塞等待服务器应答,服务器应答一个SYN-ACK段,客户端收到后从connect()返回,同时应答一个ACK段,服务器收到后从accept()返回。
数据传输的过程:
建立连接后,TCP协议提供全双工的通信服务,但是一般的客户端/服务器程序的流程是由客户端主动发起请求,服务器被动处理请求,一问一答的方式。因此,服务器从accept()返回后立刻调用read(),读socket就像读管道一样,如果没有数据到达就阻塞等待,这时客户端调用write()发送请求给服务器,服务器收到后从read()返回,对客户端的请求进行处理,在此期间客户端调用read()阻塞等待服务器的应答,服务器调用write()将处理结果发回给客户端,再次调用read()阻塞等待下一条请求,客户端收到后从read()返回,发送下一条请求,如此循环下去。
如果客户端没有更多的请求了,就调用close()关闭连接,就像写端关闭的管道一样,服务器的read()返回0,这样服务器就知道客户端关闭了连接,也调用close()关闭连接。注意,任何一方调用close()后,连接的两个传输方向都关闭,不能再发送数据了。如果一方调用shutdown()则连接处于半关闭状态,仍可接收对方发来的数据。
在学习socket API时要注意应用程序和TCP协议层是如何交互的:应用程序调用某个socket函数时TCP协议层完成什么动作,比如调用connect()会发出SYN段应用程序如何知道TCP协议层的状态变化,比如从某个阻塞的socket函数返回就表明TCP协议收到了某些段,再比如read()返回0就表明收到了FIN段

利用Java的Socket类

ServerSocket 类的构造方法:

ServerSocket() 创建未绑定的服务器套接字
ServerSocket(int port) 创建绑定到指定端口的服务器套接字 
ServerSocket(int port,int backlog) 创建服务器套接字并将其绑定到指定的本地端口    号,并指定了积压。 
ServerSocket(int port,int backlog,InetAddress bindAddr) 创建一个具有指定端口    的服务器,侦听backlog和本地IP地址绑定。 

Socket类的构造方法:

Socket() 创建一个未连接的套接字,并使用系统默认类型的SocketImpl。
Socket(InetAddress address, int port) 创建流套接字并将其连接到指定IP地址的指定    端口号。
Socket(InetAddress address, int port, InetAddress localAddr, int localPort) 创建套接    字并将其连接到指定的远程端口上指定的远程地址。
Socket(Proxy proxy) 创建一个未连接的套接字,指定应该使用的代理类型(如果有    的话),无论其他任何设置如何。
Socket(SocketImpl impl) 使用用户指定的SocketImpl创建一个未连接的Socket
Socket(String host, int port) 创建流套接字并将其连接到指定主机上的指定端口号。
Socket(String host, int port, InetAddress localAddr, int localPort)创建套接字并将其    连接到指定远程端口上的指定远程主机。 

Socket通信的基本原理:

avatar

思路:客户端使用Socket类新建一个Client对象,服务端使用ServerSocket类新建一个Server对象,使用IO流完成数据的传输和交互,客户端的输出流为服务端的输入流;服务端的输出流为客户端的输入流。

Socket编程使用TCP协议的基本步骤:

服务器创建ServerSocket,在指定端口监听并处理请求
客户端创建socket,向服务器发送请求

avatar

四、程序主要流程图


服务器端(Server)

avatar

客户端(Client)

avatar

五、程序代码

服务端(ChatServer.java)

1.新建一个ChatServer类完成服务端的编写

2.指定端口使用SocketServer创建服务器,阻塞式等待连接accept

public static void main(String[] args) throws IOException {
        System.out.println("-----Server-----");
        // 1、指定端口 使用ServerSocket创建服务器
        ServerSocket server =new ServerSocket(8889);
        // 2、阻塞式等待连接 accept
        while(true) {
                Socket  client =server.accept(); 
                System.out.println("一个客户端建立了连接");
                Channel c = new Channel(client);
                all.add(c);//使用容器管理所有成员客户端
                new Thread(c).start();            
            }        
        }
}

3.使用CopyOnWriteArrayList创建容器all从而管理所有的客户端成员

Private static CopyOnWriteArrayList<Channel> all = new CopyOnWriteArrayList<Channel>();

4.新建Channel类一个客户代表一个Channel并新建receive()方法接受消息,send()方法发送消息,sendOthers()方法完成私聊和群聊,release()方法释放资源。

/一个客户代表一个Channel
    static class Channel implements Runnable{
            private DataInputStream dis;
            private DataOutputStream dos;
            private Socket  client;            
            private boolean isRunning;
            private String name;
            public Channel(Socket  client) {
                this.client = client;
                try {
                    dis = new DataInputStream(client.getInputStream());
                    dos =new DataOutputStream(client.getOutputStream());
                    isRunning =true;
                    //获取名称
                    this.name = receive();
                    //欢迎你的到来
                    this.send("欢迎你的到来");
                    sendOthers(this.name+"来到了本聊天室",true);
                } catch (IOException e) {
                    System.out.println("---1------");
                    release();                    
                }            
            }

            //接收消息
            private String receive() {
                String msg ="";
                try {
                    msg =dis.readUTF();
                } catch (IOException e) {
                    System.out.println("---2------");
                    release();
                }
                return msg;
            }

            //发送消息
            private void send(String msg) {

                //时间显示
                Date date = new Date();
                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm");
                String currentTime = sdf.format(date);

                try {
                    dos.writeUTF(currentTime+"\r\n"+msg);
                    dos.flush();
                } catch (IOException e) {
                    //System.out.println("---3------");
                    release();
                }
            }



            //群聊
            //私聊约定数据格式:@xxx:msg
            private void sendOthers(String msg,boolean isSys) {
                boolean isPrivate = msg.startsWith("@");
                if(isPrivate) { //私聊
                    int idx = msg.indexOf(":");
                    //获取目标和数据
                    String targetName = msg.substring(1,idx);
                    msg = msg.substring(idx+1);
                    for(Channel other:all) {
                        if(other.name.equals(targetName)) {
                            other.send(this.name+"私聊你: "+msg);
                            break;
                        }
                    }
                }
                else {
                    for(Channel other:all) {
                        if(other==this) {//自己
                            continue;
                        }
                        if(!isSys) {
                            other.send(this.name+":"+msg);//群聊消息
                        }
                        else {
                            other.send(msg); //系统消息
                        }
                    }
                }    
            }
            //释放资源
            private void release() {
                this.isRunning = false;
                ChatUtils.close(dis,dos,client);
                //退出
                all.remove(this);
                sendOthers(this.name+"离开了本聊天室", false);
            }
            @Override
            public void run() {
                while(isRunning) {
                    String msg = receive();
                    if(!msg.equals("")) {
                        sendOthers(msg,false);
                    }
                }
            }

客户端(Client.java)

1.新建Client类完成客户端的编写

2.输入用户名新建连接,使用Socket创建客户端和指定的服务器端口和地址

3.客户端发送消息

public class Client {
    public static void main(String[] args) throws UnknownHostException,                 IOException {
        System.out.println("-----Client-----");
        BufferedReader br = new BufferedReader(new     InputStreamReader(System.in));
        System.out.println("请输入用户名:");
        String name = br.readLine();
        //1.建立连接,使用Socket创建客户端+服务的地址和端口
        Socket client = new Socket("localhost",8889);
        //2.客户端发送消息
        new Thread(new Send(client,name)).start();
        new Thread(new Receive(client)).start();
    }
}

Receive类(Receive.java)

1.使用多线程封装接收端

方法有接收消息,释放资源,重写run方法

public class Receive implements Runnable {
    private DataInputStream dis ;
    private Socket client;
    private boolean isRunning;
    public Receive(Socket client) {
        this.client = client;
        this.isRunning = true;
        try {
            dis =new DataInputStream(client.getInputStream());
        } catch (IOException e) {
            System.out.println("====2=====");
            release();
        }
    }

    //接收消息
    private String receive() {
        String msg ="";
        try {
            msg =dis.readUTF();
        } catch (IOException e) {
            System.out.println("====4====");
            release();
        }
        return msg;
    }

    @Override
    public void run() {        
        while(isRunning) {
            String msg =receive();
            if(!msg.equals("")) {
                System.out.println(msg);
            }
        }
    }
    //释放资源
    private void release() {
        this.isRunning = false;
        ChatUtils.close(dis,client);
    }
}

Send类(Send.java)

1.使用多线程编写发送端

2.方法有从控制台获取信息,释放资源,重写多线程的run方法

public class Send implements Runnable{
    private BufferedReader console;
    private DataOutputStream dos;
    private Socket client;
    private boolean isRunning;
    private String name;
    public Send(Socket client,String name) {
        this.client = client;
        console = new BufferedReader(new InputStreamReader(System.in));
        this.isRunning = true;
        this.name = name;
        try {
            dos = new DataOutputStream(client.getOutputStream());
            //发送名称
            send(name);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            this.release();
        }
    }

    //从控制台获取消息
    private String getStrFromConsole() {
        try {
            return console.readLine();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return "";
    }

    //发送消息
    private void send(String msg) {
        try {
            dos.writeUTF(msg);
            dos.flush();
        } catch (IOException e) {
            System.out.println("---client------");
            release();
        }
    }

    //释放资源
    private void release() {
        this.isRunning = false;
        ChatUtils.close(dos,client);
    }
    @Override
    public void run() {
        while(isRunning) {
            String msg = getStrFromConsole();
            if(!msg.equals("")) {
                send(msg);
            }
        }
    }

}

工具类(ChatUtils.java)

1.释放资源的工具类


public class ChatUtils {
    //释放资源
    public static void close(Closeable ...targets) {
        for(Closeable target:targets) {
            try {
                if(null != target) {
                    target.close();
                }
            }catch (Exception e) {
                // TODO: handle exception
                e.printStackTrace();
            }
        }
    }
}

运行效果

avatar

END

修改百度开放平台人脸识别错误[Python]json 错误xx is not JSON serializable

修改百度开放平台人脸识别错误[Python]json 错误xx is not JSON serializable

简介

百度AI开放平台所用到的人脸识别功能,人体识别功能很多人特别喜欢用,但是!会遇到如下的错误:

[Python]json 错误xx is not JSON serializable

那么如何解决这个问题呢?

很简单,如下所示:

首先打开你的terminal;windows的同学打开你的powershell或者cmd

如下所示 输入:

    pip3 show baidu-aip
    或
    pip show baidu-aip

avatar

找到location的位置进入该位置

然后进入site-packages位置后输入:

cd aip
ls

如下所示:

avatar

找到face.py 文件 用vim打开

vim face.py

在文本结尾输入如下代码

class MyEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, bytes):
            return str(obj, encoding='utf-8')
        return json.JSONEncoder.default(self, obj)

然后找到其中的 所有每个方法 例如match方法 在json.dump()那一行改为如下所示

avatar

在False后加入如下代码

cls = MyEncoder

注意空格

注意:建议将face.py中的所有方法的json.dump()中都改为如上所示的内容,防止bug再次出现

END

离散哈特莱变换(DHT)

author: RoWe98(RexRowe)

离散哈特莱变换(DHT)

摘 要

离散哈特莱变换(DHT)是一种与傅里叶变换相关的转换。类似于离散傅里叶变换。与傅里叶变换在信号处理及其他相关领域有相似的应用。
本设计介绍了DHT的定义以及使用C语言实现其算法。

关键字:傅里叶变换 哈特莱变换

二、设计平台

  • Linux平台
  • GCC编译器
  • vim
  • windows
  • Visual Studio 2017

三、设计原理

DHT的定义
设$\mathrm{x}(\mathrm{n}), \mathrm{n}=0,1, \ldots, \mathrm{N}-1 \mathrm{x}(\mathrm{n}), \mathrm{n}=0,1, \ldots, \mathrm{N}-1$为一实序列,其DHT定义为:

$$
\begin{aligned} X_{H}(k) &=\operatorname{DHTEx(n)} ] \ &=\sum_{n=0}^{N-1} x(n) \operatorname{cas}\left(\frac{2 \pi}{N} k n\right), \quad k=0,1, \cdots, N-1 \end{aligned}
$$

式中$\operatorname{cas}(a)=\cos (a)+\sin (a) \operatorname{cas}(a)=\cos (a)+\sin (a)$逆变换$(\mathrm{IDHT})$为:

$$
\begin{aligned} x(n) &=\operatorname{IDHT}\left[X_{H}(k)\right] \ &=(1 / N) \sum_{k=0}^{N-1} X_{H}(k) \operatorname{cas}\left(\frac{2 \pi}{N} k n\right), \quad n=0,1, \cdots, N-1 \end{aligned}
$$

DHT的正交证明:

$$
\sum_{k=0}^{N-1} \operatorname{cas}\left(\frac{2 \pi}{N} k n\right) \operatorname{cas}\left(\frac{2 \pi}{N} k m\right)=\sum_{k=0}^{N-1}\left[\cos \left(\frac{2 \pi}{N} k(n-m)\right)+\sin \left(\frac{2 \pi}{N} k(n+m)\right)\right]=\left{\begin{array}{ll}{N,} & {k=0} \ {0,} & {k \neq 0}\end{array}\right.
$$

DHT和DFT关系
用X(k)表示实序列x(n)的,用XH(k)表示x(n)的DHT,分别用XHe(k), XHo(k)表示XH(k)的偶对称分量与奇对称分量,即:

$X_{\mathrm{H}}(k)=X_{\mathrm{He}}(k)+X_{\mathrm{Ho}}(k)$

其中:

$$
\begin{array}{c}{X_{H e}(k)=\frac{1}{2}\left[X_{H}(k)+X_{H}(N-k)\right]}{X_{H o}(k)=\frac{1}{2}\left[X_{H}(k)-X_{H}(N-k)\right]} \ {X(k)=X_{H e}(k)-j X_{H o}(k)} \ {X_{H}(k)=\operatorname{Re}[X(k)]-\operatorname{Im}[X(k)]} \ {X(k)=\frac{1}{2}\left[X_{H}(k)+X_{H}(N-k)\right]-\frac{1}{2} j\left[X_{H}(k)-X_{H}(N-k)\right]}\end{array}
$$

DHT的优点

  • DHT为实值,避免了复数运算

  • DHT正反变换形式基本一致

  • DHT与DFT的转换容易实现

DHT的性质

DHT的性质与DFT的性质类似,但由于DHT是实序列间的变换,有些性质有具体的表达形式。这里只给出结论。
设x(n)、y(n)的DHT分别为Xh(k)、Yh(k)。用符号x(n)↔Xh(k)表示Xh(k)=DHT(x(n))。

1.线性性

2.逆序列x(N-n)的DHT
$x(N-n) \leftrightarrow X_{\mathrm{H}}(\mathrm{N}-k)$


$$
\mathrm{X}{\mathrm{H}}(\mathrm{N}-\mathrm{k})=\sum{n=0}^{\mathrm{N}-1} x(n)\left[\cos \left(\frac{2 \pi}{\mathrm{N}} k n\right)-\sin \left(\frac{2 \pi}{N} k n\right)\right], \quad k=0,1, \cdots, N-1
$$

当k=0时,可得Xh(N)=Xh(0)。

3.循环位移的性质
$$
\begin{array}{l}{x\left(\left(n-n_{0}\right){N} R{N}(n) \leftrightarrow X_{\mathrm{H}}(k) \cos \left(\frac{2 \pi}{N} k n_{0}\right)+X_{\mathrm{H}}(N-k) \sin \left(\frac{2 \pi}{N} k n_{0}\right)\right)} \ {x\left(\left(n+n_{0}\right){N} R{N}(n) \leftrightarrow X_{\mathrm{H}}(k) \cos \left(\frac{2 \pi}{N} k n_{0}\right)-X_{\mathrm{H}}(N-k) \sin \left(\frac{2 \pi}{N} k n_{0}\right)\right)}\end{array}
$$

四、实现代码

【C语言】

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

const int N = 1024;
const float PI = 3.1416;

inline void swap(float &a, float &b)
{
    float t;
    t = a;
    a = b;
    b = t;
}

void bitrp(float xreal[], float ximag[], int n)
{
    // 位反转置换 Bit-reversal Permutation
    int i, j, a, b, p;

    for (i = 1, p = 0; i < n; i *= 2)
    {
        p++;
    }
    for (i = 0; i < n; i++)
    {
        a = i;
        b = 0;
        for (j = 0; j < p; j++)
        {
            b = (b << 1) + (a & 1);    // b = b * 2 + a % 2;
            a >>= 1;        // a = a / 2;
        }
        if (b > i)
        {
            swap(xreal[i], xreal[b]);
            swap(ximag[i], ximag[b]);
        }
    }
}

void FFT(float xreal[], float ximag[], int n)
{
    // 快速傅立叶变换,将复数 x 变换后仍保存在 x 中,xreal, ximag 分        //    别是 x 的实部和虚部
    float wreal[N / 2], wimag[N / 2], treal, timag, ureal, uimag, arg;
    int m, k, j, t, index1, index2;

    bitrp(xreal, ximag, n);

    // 计算 1 的前 n / 2 个 n 次方根的共轭复数 W'j = wreal [j] + i *         wimag [j] , j = 0, 1, ... , n / 2 - 1
    arg = -2 * PI / n;
    treal = cos(arg);
    timag = sin(arg);
    wreal[0] = 1.0;
    wimag[0] = 0.0;
    for (j = 1; j < n / 2; j++)
    {
        wreal[j] = wreal[j - 1] * treal - wimag[j - 1] * timag;
        wimag[j] = wreal[j - 1] * timag + wimag[j - 1] * treal;
    }

    for (m = 2; m <= n; m *= 2)
    {
        for (k = 0; k < n; k += m)
        {
            for (j = 0; j < m / 2; j++)
            {
                index1 = k + j;
                index2 = index1 + m / 2;
                t = n * j / m;    // 旋转因子 w 的实部在 wreal [] 中                    //的下标为 t
                treal = wreal[t] * xreal[index2] - wimag[t] *                         ximag[index2];
                timag = wreal[t] * ximag[index2] + wimag[t] *                         xreal[index2];
                ureal = xreal[index1];
                uimag = ximag[index1];
                xreal[index1] = ureal + treal;
                ximag[index1] = uimag + timag;
                xreal[index2] = ureal - treal;
                ximag[index2] = uimag - timag;
            }
        }
    }
}



void FFT_test()
{
    float xreal[N] = {}, ximag[N] = {};

    int n = 8;
    int i = 0;
    printf("请输入数据,格式(实部 虚部) : \n");
    for (i = 0; i < 8; i++)
    {
        scanf("%f%f",xreal+i,ximag+i);
    }

    n = i;    // 要求 n 为 2 的整数幂
    while (i > 1)
    {
        if (i % 2)
        {
            printf("%d is not a power of 2! ", n);
        }
        i /= 2;
    }

    FFT(xreal, ximag, n);
    printf("=====================================\n");
    printf("FFT:    i          实部       虚部 \n");
    for (i = 0; i < n; i++)
    {
        printf("     %4d       %8.4f    %8.4f ", i+1, xreal[i],             ximag[i]);
        printf("\n");
    }
    printf("===================================== \n");


    printf("DHT:    i        结果 \n");
    for (i = 0; i < n; i++)
    {
        printf("     %4d        %8.4f ", i+1, xreal[i]-ximag[i]);
        printf("\n");
    }
    printf("=====================================\n ");
}

int main()
{
    FFT_test();
    system("pause");
    return 0;
}

运行效果

请输入数据,格式(实部 虚部) : 
1 0
2 0
3 0
4 0
5 0
6 0
7 0
8 0
=====================================
FFT:    i          实部       虚部 
        1        36.0000      0.0000 
        2        -4.0000      9.6568 
        3        -4.0000      4.0000 
        4        -4.0000      1.6569 
        5        -4.0000      0.0000 
        6        -4.0000     -1.6568 
        7        -4.0000     -4.0000 
        8        -4.0001     -9.6569 
===================================== 
DHT:    i        结果 
        1         36.0000 
        2        -13.6568 
        3         -8.0000 
        4         -5.6568 
        5         -4.0000 
        6         -2.3432 
        7         -0.0000 
        8          5.6568 
=====================================

End

PyQt入门(第一个PyQt应用)

PyQt入门(第一个PyQt应用)

一、PyQt介绍

PyQt是一个创建GUI应用程序的工具包。它是Python编程语言和Qt库的成功融合。Qt库是目前最强大的库之一。PyQt是由Phil Thompson 开发。

PyQt实现了一个Python模块集。它有超过300类,将近6000个函数和方法。它是一个多平台的工具包,可以运行在所有主要操作系统上,包括UNIX,Windows和Mac。 PyQt采用双许可证,开发人员可以选择GPL和商业许可。在此之前,GPL的版本只能用在Unix上,从PyQt的版本4开始,GPL许可证可用于所有支持的平台。

二、python GUI框架简介,pyqt介绍,为什么使用pyqt

python开发GUI程序,了解一个框架需要了解:

(1)运行效果
(2)能够运行在哪些平台
(3)学习成本,因为每一个GUI框架都有自己的特点
(4)开发效率的比较,python的运行效率比c c++低,但是开发效率是比较快的

python GUI框架:

(1)Tkinter: python内置的GUI框架,使用TCL实现,python中内嵌了TCL解释器,使用它的时候不用安装额外的扩展包,直接import,跨平台。不足之处在于UI布局全靠代码实现,只有15种常用部件,显示效果简陋。
(2)Wxpython:用得比较广泛,跨平台,C++编写,需要安装扩展模块;文档少,遇到问题不好解决,代码布局控件,不直观。
(3)Pygtk: python对GTK+GUI库的封装,在linux平台上运行的比较好,需要安装扩展模块,在windows下的兼容性有一些问题。
(4)pyqt:QT原本是诺基亚的产品,源码用C++写的,python对QT的包装,跨平台,本地显示效果,根据系统决定,在win7下就是win7的显示效果;pyqt与qt的函数接口一致,qt开发问的那个丰富,所以pyqt开发文档也比较丰富;控件丰富,函数/方法多,拖曳布局;方便打包成二进制文件;GPL协议,商业程序需要购买商业版授权
(5)pyside:诺基亚的亲儿子,python对QT的封装,安装扩展模块,跨平台,与pyqt的API一样,LGPL协议,新软件可以是私有的,代码布局
(6)Kivy: 针对多点触控程序,智能手机平板等,也可以在没有触屏功能的系统上,全平台支持;使用python和cython(python和c语言的接口)编写;中文支持差,需要自己下载中文库并且制定路径。

为什么使用pyqt:

  1. 因为API与qt一致,学会了pyqt再使用qt很简单
  2. 文档丰富
  3. 学习成本低
  4. 开发迅速,qt designer拖曳布局,如果使用代码布局还需要构建全局并且调试
  5. 学习经验容易迁移到pyside来开发商业应用
  6. 方便打包发布软件,python本身解释语言的特点是写好的程序不编译和链接,使用文本运行解释器,边解释边执行,用户不可能装一个解释器再发源码再执行,所以打包成exe再发布

三、安装与配置

1.安装

安装pyqt5

pip install pyqt5 or pip3 install pyqt5

Windows安装PyQt-tools

pip3 install pyqt5-tools

Mac和Linux都不支持PyQt5-tools所以直接安装Qt Designer即可

  • Mac

    pip install Qt

  • Linux

    注:由于我用的Manjaro已经内置了Qt Designer,所以这里写ubuntu下的安装方法

    sudo apt-get install qt5-default qttools5-dev-tools

terminal中查看是否安装成功PyQt

 rex@rex-linux  ~  pip list
Package              Version 
-------------------- --------
appdirs              1.4.3   
btrfsutil            1.1.1   
CacheControl         0.12.5  
chardet              3.0.4   
colorama             0.4.1   
cupshelpers          1.0     
distlib              0.2.9   
distro               1.4.0   
docopt               0.6.2   
html5lib             1.0.1   
idna                 2.8     
Jade-Application-Kit 2.0.7   
keyutils             0.5     
lockfile             0.12.2  
louis                3.10.0  
msgpack              0.6.1   
npyscreen            4.10.5  
numpy                1.16.4  
opencv-python        4.1.0.25
packaging            19.0    
pacman-mirrors       4.14.2  
pep517               0.5.0   
Pillow               6.1.0   
pip                  19.0.3  
progress             1.5     
pwquality            1.4.0   
pycairo              1.18.1  
pycups               1.9.74  
pycurl               7.43.0.2
PyGObject            3.32.2  
pyparsing            2.4.0   
PyQt5                5.13.0  
PyQt5-sip            4.19.18 
pysmbc               1.0.16  
pytoml               0.1.20  
PyYAML               5.1     
reportlab            3.5.23  
requests             2.22.0  
retrying             1.3.3   
setuptools           41.0.1  
six                  1.12.0  
team                 1.0     
udiskie              1.7.7   
urllib3              1.25.3  
webencodings         0.5.1 

如图所示我们已经安装成功了PyQt5

2.配置

若你已经可以在你的应用程序菜单里找到Qt Designer那就可以了

PyCharm配置

注:PyCharm安装激活方法自行百度,这里不提供

  • 打开PyCharm
  • File->Setting->Tools->External Tools

  • 点击加号,依次填入

    Name:Qt Designer
    
    Group:Qt
    
    Program:usr/local/designer
    
    Arguements:$FileName$
    
    Working directory:$ProjectFileDir$
    

  • 再次点击加号输入

    Name:PyUIC
    
    Group:Qt
    
    Program:/usr/bin/python3
    
    Arguements:-m PyQt5.uic.pyuic $FileName$ -o $FileNameWithoutExtension$.py
    
    Working directory:$FileDir$
    

  • Ok->Apply

到这里PyCharm的配置基本结束

四、第一个PyQt GUI应用程序

1.UI设计

  • 打开PyCharm新建一个基于venv的python项目

  • 在Setting的Project interpreter中查看是否有PyQt没有的话自行添加

  • 打开Tools->QT->Qt Designer

  • 新建一个MainWindow ui界面

  • 向上拖拉控件并保存名为firstMainWindow.ui

2.ui文件转换为.py文件与代码编写和运行

要想.ui文件可以被我们程序读取有两种方法

  • 使用命令行来将.ui编译为.py文件

    pyuic5 -o firstMainWindow.py firstMainWindow.ui
    

    编译完成后的firstMainWindow.py文件内容

  from PyQt5 import QtCore, QtGui, QtWidgets


  class Ui_MainWindow(object):
      def setupUi(self, MainWindow):
          MainWindow.setObjectName("MainWindow")
          MainWindow.resize(800, 600)
          self.centralwidget = QtWidgets.QWidget(MainWindow)
          self.centralwidget.setObjectName("centralwidget")
          self.listWidget = QtWidgets.QListWidget(self.centralwidget)
          self.listWidget.setGeometry(QtCore.QRect(140, 60, 531, 371))
          self.listWidget.setObjectName("listWidget")
          MainWindow.setCentralWidget(self.centralwidget)
          self.menubar = QtWidgets.QMenuBar(MainWindow)
          self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 30))
          self.menubar.setObjectName("menubar")
          self.menuFile = QtWidgets.QMenu(self.menubar)
          self.menuFile.setObjectName("menuFile")
          self.menuEdit = QtWidgets.QMenu(self.menubar)
          self.menuEdit.setObjectName("menuEdit")
          MainWindow.setMenuBar(self.menubar)
          self.statusbar = QtWidgets.QStatusBar(MainWindow)
          self.statusbar.setObjectName("statusbar")
          MainWindow.setStatusBar(self.statusbar)
          self.actionNew = QtWidgets.QAction(MainWindow)
          self.actionNew.setObjectName("actionNew")
          self.actionOpen = QtWidgets.QAction(MainWindow)
          self.actionOpen.setObjectName("actionOpen")
          self.menuFile.addAction(self.actionNew)
          self.menuFile.addAction(self.actionOpen)
          self.menubar.addAction(self.menuFile.menuAction())
          self.menubar.addAction(self.menuEdit.menuAction())

          self.retranslateUi(MainWindow)
          QtCore.QMetaObject.connectSlotsByName(MainWindow)

      def retranslateUi(self, MainWindow):
          _translate = QtCore.QCoreApplication.translate
          MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
          self.menuFile.setTitle(_translate("MainWindow", "File"))
          self.menuEdit.setTitle(_translate("MainWindow", "Edit"))
          self.actionNew.setText(_translate("MainWindow", "New"))
          self.actionOpen.setText(_translate("MainWindow", "Open"))

  • 使用代码直接读取.ui文件

    这里我们还是依旧两种方法来读取.ui文件

    1.使用我们编译好的firstMainWindow.py文件

    2.直接读取.ui文件

    • 使用firstMainWindow.py文件

      编写一个叫CallFirstMainWin.py文件,内容如下

    import sys
    from PyQt5.QtWidgets import  QApplication, QMainWindow
    from firstMainWin import *
    class MyMainWindow(QMainWindow, Ui_MainWindow):


        def __init__(self, parent=None):
            super(MyMainWindow, self).__init__(parent)
            self.setupUi(self)


    if __name__ == "__main__":
        app=QApplication(sys.argv)
        myWin = MyMainWindow()
        myWin.show()
        sys.exit(app.exec_())
这里我们继承了firstMainWindow.py中的Ui_MainWindow类来使用我们设计的UI
  • 直接读取.ui文件
    import sys
    from PyQt5.QtWidgets import  QApplication, QMainWindow
    from firstMainWin import *
    from PyQt5.uic import loadUi

    app = QApplication(sys.argv)
    widget = loadUi('firstMainWin.ui')
    widget.show()
    sys.exit(app.exec_())

这里是直接使用了```loadUi```方法来读取了ui文件

注意:为了保障界面与逻辑分离我们大部分情况下都使用第一种方法,通过继承界面文件的主窗口类来完成界面和逻辑的分离

  • 运行

两种方式都可以成功运行

End

论如何在linux上正确驱动博通网卡

论如何在linux上正确驱动博通网卡

写在前面

我的网卡型号是BCM94360,前几天安装了基于Arch Linux的Linux发行版Manjaro出现了无法识别无线网卡驱动的问题

➜  ~ lspci | grep -i net            
00:19.0 Ethernet controller: Intel Corporation Ethernet Connection (3) I218-LM (rev 03)
03:00.0 Network controller: Broadcom Inc. and subsidiaries BCM4360 802.11ac Wireless Network Adapter (rev 03)

如上所示,在命令行输入lspci | grep -i net后可以查看自己的无线网卡的型号,我的型号为BCM4360

问题描述

  • 找不到无线网络
  • 找不到无线开关

解决

在我通过bing,baidu,google后找到了基本的解决思路,是因为没有安装对应的网卡驱动

安装驱动

1.更新系统软件
sudo pacman -Syu

2.安装对应的linux-headers

  • 在设置中找到自己系统版本对应的内核

    如上所示我的内核版本为Linux 4.19.59-1

  • sudo pacman -S linux-headers
    这里选择对应上面的Linux 4.19.59-1版本的即可

  • 安装博通网卡驱动sudo pacman -S broadcom-wl-dkms

3.重启

重启过后继续继续输入dkms status

若显示如下则说明安装成功

➜  ~ dkms status                    
broadcom-wl, 6.30.223.271, 4.19.59-1-MANJARO, x86_64: installed

到这大部分博通的网卡应该都可以解决wifi的问题

但是

我出现了新的问题,搜索不到wifi

WDNMD!!

解决方法

淘宝买了一个linux免驱的无线网卡

怎么说? WDNMD! 白给了!

世界第一linux发行版——Manjaro

世界第一的linux发行版 —— Manjaro

写在前面

也算是了却了自己的一个心事吧,之前一直想装个Arch Linux玩玩但是Arch Linux的安装实在是特别繁琐,于是在google的过程中发现了Linux发行版的排行榜,发现了Manjaro竟然已经到了全球第一的地位。Manjaro这个Linux发现版我之前也算了解过。

介绍

Manjaro官方版本

  • xfce版(64)
  • kde版(64)
  • gnome版(64)
  • Architect版
  • Arm版
  • 32位版(xfce)
  • 社区版(deepin桌面版,等等)

这些版本你在Manjaro的官方网站的下载页面可以查看到具体的区别

安装

1.下载镜像

Manjaro下载地址

2.刻录镜像

这里我使用的是refus这个刻录软件

这里是截取的官方的图所以显示ubuntu不要纠结
如果弹窗提示请选择dd

3.安装

这里简单说说基本就是这几个步骤

  • u盘启动
  • 选择安装
  • 重启

系统配置

先给大家看看我安装之后的样子

先排列源

sudo pacman-mirrors -g

同步并优化(类似磁盘整理,固态硬盘无需操作)
sudo pacman-optimize && sync
升级系统
sudo pacman -Syyu
添加中科大源
打开配置文件在文件末尾添加

sudo nano /etc/pacman.conf
[archlinuxcn]
SigLevel = Optional TrustedOnly
Server = https://mirrors.ustc.edu.cn/archlinuxcn/$arch

导入GPG Key
sudo pacman -Syy && sudo pacman -S archlinuxcn-keyring
现在可以安装软件了,比如 chrome 和搜狗拼音输入法

安装搜狗输入法

sudo pacman -S fcitx-sogoupinyin
sudo pacman -S fcitx-im
sudo pacman -S fcitx-configtool # 图形化的配置工具

需要修改配置文件 ~/.xprofile
添加如下语句

export GTK_IM_MODULE=fcitx
export QT_IM_MODULE=fcitx
export XMODIFIERS="@im=fcitx"

安装包管理工具yaourt
pacman -S yaourt

这个管理工具可以帮助你安装aur源上的程序

通过这个工具你可以安装deepin打包的各种软件,例如:

  • 微信
  • QQ
  • 百度网盘
  • 等等

通过下面的语句安装

yaourt -S deepin-wine-qq
yaourt -S deepin-wine-pan
yaourt -S deepin-wine-wechat

安装的话就是输入对应的你想安装的程序前的序号即可

修复kde桌面运行deepin-wine程序闪退的错误

在非gnome桌面环境下,Deepin-Wine软件大部分不能启动,例如deepin-Tim,deepin-QQ以及微信等。按照DLM的说法,深度在打包Deepin-Wine软件时加入了对Gnome的依赖。通过安装gnome-settings-daemon即可解决。

1.安装gnome-settings-daemon
sudo apt install gnome-settings-daemon

2.复制org.gnome.SettingsDaemon.XSettings.desktop
cp /etc/xdg/autostart/org.gnome.SettingsDaemon.XSettings.desktop ~/.config/autostart

3.设置org.gnome.SettingsDaemon.XSettings.desktop开机自启

在KDE中的一个简单的配置方法是:打开系统设置->开机和关机->自动启动->设置为已启用->最后注销登录或者重启电脑即可打开Deepin-Wine软件。(其他非KDE桌面环境,方法自己参考)

记得选择高级中的如下选项

然后你就可以正常运行deepin打包的wine软件了

其他

常用软件安装

谷歌浏览器
pacman -S google-chrome
国内版火狐浏览器
pacman -S firefox firefox-i18n-zh-cn
压缩解压缩
pacman -S file-roller unrar unzip p7zip
Git ssh
pacman -S git openssh
安装wps
yaourt -S wps-office
VSCode
pacman -S visual-studio-code-bin 

pacman和yaourt常用命令

安装 pacman -S
删除 pacman -R
移除已安装不需要软件包 pacman -Rs
删除一个包,所有依赖 pacman -Rsc
升级包 pacman -Syu
查询包数据库 pacman -Ss
搜索以安装的包 pacman -Qs
显示包大量信息 pacman -Si
本地安装包 pacman -Qi
清理包缓存 pacman -Sc 

End

C#学习笔记

C#学习笔记

C#程序结构

C# 中的关键组织结构概念包括程序命名空间类型成员程序集。 C# 程序由一个或多个源文件组成。 程序声明类型,而类型则包含成员,并被整理到命名空间中。 类型示例包括类和接口。 成员示例包括字段、方法、属性和事件。 编译完的 C# 程序实际上会打包到程序集中。 程序集的文件扩展名通常为 .exe.dll,具体取决于实现的是应用程序还是

以下示例在 Acme.Collections 命名空间中声明 Stack 类:

C#

using System;
namespace Acme.Collections
{
    public class Stack
    {
        Entry top;
        public void Push(object data) 
        {
            top = new Entry(top, data);
        }

        public object Pop() 
        {
            if (top == null)
            {
                throw new InvalidOperationException();
            }
            object result = top.data;
            top = top.next;
            return result;
        }

        class Entry
        {
            public Entry next;
            public object data;
            public Entry(Entry next, object data)
            {
                this.next = next;
                this.data = data;
            }
        }
    }
}

此类的完全限定的名称为 Acme.Collections.Stack。 此类包含多个成员:一个 top 字段、两个方法(PushPop)和一个 Entry 嵌套类。 Entry 类还包含三个成员:一个 next 字段、一个 data 字段和一个构造函数。 假定示例的源代码存储在 acme.cs 文件中,以下命令行

csc /t:library acme.cs

将示例编译成库(不含 Main 入口点的代码),并生成 acme.dll 程序集。

重要

上述示例使用 csc 作为命令行 C# 编译器。 此编译器是 Windows 可执行文件。 若要在其他平台上使用 C#,应使用 .NET Core 工具。 .NET Core 生态系统使用 dotnet CLI 来管理命令行生成。 这包括管理依赖项和调用 C# 编译器。 有关在 .NET Core 支持的平台上使用这些工具的完整说明,请参阅这篇教程

程序集包含中间语言 (IL) 指令形式的可执行代码和元数据形式的符号信息。 执行前,程序集中的 IL 代码会被 .NET 公共语言运行时的实时 (JIT) 编译器自动转换成处理器专属代码。

由于程序集是包含代码和元数据的自描述功能单元,因此无需在 C# 中使用 #include 指令和头文件。 只需在编译程序时引用特定的程序集,即可在 C# 程序中使用此程序集中包含的公共类型和成员。 例如,此程序使用 acme.dll 程序集中的 Acme.Collections.Stack 类:

C#

using System;
using Acme.Collections;
class Example
{
    static void Main() 
    {
        Stack s = new Stack();
        s.Push(1);
        s.Push(10);
        s.Push(100);
        Console.WriteLine(s.Pop());
        Console.WriteLine(s.Pop());
        Console.WriteLine(s.Pop());
    }
}

如果程序存储在文件 example.cs 中,那么在 example.cs 编译完后,可以使用编译器的 /r 选项引用 acme.dll 程序集:

复制

csc /r:acme.dll example.cs

这会创建 example.exe 可执行程序集,它将在运行时输出以下内容:

复制

100
10
1

使用 C#,可以将程序的源文本存储在多个源文件中。 编译多文件 C# 程序时,可以将所有源文件一起处理,并且源文件可以随意相互引用。从概念上讲,就像是所有源文件在处理前被集中到一个大文件中一样。 在 C# 中,永远都不需要使用前向声明,因为声明顺序无关紧要(除了极少数的例外情况)。 C# 并不限制源文件只能声明一种公共类型,也不要求源文件的文件名必须与其中声明的类型相匹配。