课否 · 回复功能开发实践(三): 对于业务的思考

这个月大部分闲暇时间都在开发《课否》的这个回复功能,今天终于把完整的代码commit并且push掉了。在这期间,我走了一些弯路,同时也有了一些感悟,想要记录下来。

首先先来聊一聊我们的这个回复功能到底是什么吧。

课否之前的界面是这样的,对于每一个老师展示其所有的评价。(回复按钮由我新增)

课否原界面课否原界面

我们的需求是针对每一条评价进行回复,并且对于回复也可以进行回复。我仿照了浙大云朵朵小程序的设计方式:在每一条评论的下面显示若干条回复,如果回复较多,则提供展开回复的按钮,通过点击按钮来展现左右的回复。按照这个思路,我在数据库中为每一个comment下添加了replies字段。

这里我同色老师进行过一次交流,针对设计方案的选择进行讨论。色老师提议采用微博的回复方式,在每一条评论下放置几条回复以及一个查看所有回复的按钮,点击按钮之后跳转到一个新的界面,这个界面上方呈现被评论的评价,下方呈现该评价的各种回复。

色老师的这个建议让我拨开云雾见到了青天。

在此之前,我对于数据库的设计十分纠结,不知道是该服用comment还是新建一个replies的表。按照色老师的建议,我们自然而然的想到,要新建一个replies的表,每个reply保存其root是哪个comment,而在comment下保存reply的外键数组。

解决了数据库的结构和界面的设计问题之后,我很快的完成了这些功能。

这次实践给了我很多反思。

其一是技术层面的,关于redux和state的选择。在开发时,我最开始把很多数据都一股脑塞进了redux中,后来我才回想起redux的作用是为了不同组件共享数据。于是我才想到应当把一些数据写进组件里,用state就足够了。

其二则是开发经验上的。在此之前我总觉得前端开发就是学框架、学语言。框架会用了,自然就会开发了。但是经过这次开发我才发现,我们如何完成需求往往才开发人员要做的。或者说,我们应当用怎样的方式去设计、去完成这个需求。

写到这里,我对于这篇博客很不满意。觉得自己不知所云。觉得自己没有把内心的那种感悟写出来。不知道是不是我的表达能力出了问题。

也可能是我经验还不够足,对于开发的认知也很浅薄。

总之,在以后的开发中,我应当先好好地分析需求,分析业务逻辑,再去写代码。而不是写了半天不知道自己在写什么。

课否 · 回复功能开发实践(二): React函数式组件与Hooks

最近大概阅读了一下课否的代码,项目使用了Taro框架。这个框架主要特点是可以实现小程序跨平台多端统一开发。这里对于框架不多赘述。

另外一个特点则是这次项目中组件的形态发生了较大的变化:从类组件变成了函数式组件。因此在开始开发之前,我决定先了解一下React函数式组件。

1、函数式组件概述

拿React官网上的函数式组件的Demo来举例

harmony
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import React, { useState, useEffect } from 'react';

function Example() {
const [count, setCount] = useState(0);

// 相当于 componentDidMount 和 componentDidUpdate:
useEffect(() => {
// 使用浏览器的 API 更新页面标题
document.title = `You clicked ${count} times`;
});

return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}

Example 是一个函数,它return一个React Dom。这种返回React Dom节点的函数就是函数式组件。这种函数式组件直观上要比类组件的代码简洁的多。

函数式组件与类组件最大的区别就是,类组件有生命周期,并且state和props都作为类的属性。而函数式组件则不存在这样的生命周期。因此我们要学习的主要内容就是如何在函数式组件中使用state和进行生命周期的管理。

Hooks 的作用正是如此

2、Hook 概览

同样参考上面的代码, count 变量很像我们类组件中的state。事实上useState正是帮助我们使用state的hook。而useEffect则会在渲染的时候更新一下页面的标题。

Hook 是一些可以让你在函数组件里“钩入” React state 及生命周期等特性的函数。

React内置了一些Hook,我们也可以创建自己的Hook来服用不同组件之间的状态逻辑。

接下来我们主要学习一下两个常用的Hook: State和Effect Hook

3、State Hooks

我们使用类组件来实现相同的点击功能,如下

harmony
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}

render() {
return (
<div>
<p>You clicked {this.state.count} times</p>
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
Click me
</button>
</div>
);
}
}

construtor中初始化state.count为0,使用this.state.count来获取count的值,使用this.setState()方法来更改state

调用useState钩子函数来新建一个state及修改其的方法。该函数的参数是state的初始值,以数组的形式返回state和修改state的方法。在上面的例子中,我们使用数组的解构赋值来获取到了它们。

等号左边的名字是由我们自己决定的。另外如果有多个state的话,我们可以依次声明并且使用它们。

state也不必须是基本的变量,它可以是数组或者对象。

4、 Effect Hooks

在介绍Effect Hooks之前,我们需要先了解一个新的概念: 函数副作用(side effects)

在计算机科学中,函数副作用指当调用函数时,除了返回函数值之外,还对主调用函数产生附加的影响。例如修改全局变量(函数外的变量),修改参数或改变外部存储。

与函数副作用的几个相关概念有:

  • 纯函数 输入输出的数据全是显式的,函数与外界交换数据只有唯一的渠道——参数与返回值。
  • 非纯函数 函数通过隐式的方式同外界交换数据。隐式即函数除参数与返回值之外,用其他方式同外界交换方式。比如读取全局变量、修改全局变量、IO等等
  • 参照透明性 无副作用是参照透明性的必要非充分条件。参照透明意味着一个表达式(例如一次函数调用)可以被替换为它的值。这需要该表达式是纯的,也就是说该表达式必须是完全确定的(相同的输入总是导致相同的输出)而且没有副作用。

另外如果函数参数是个引用,而函数修改了引用内的内容,则该函数同样具有副作用。比如js中某个函数的参数是一个对象,而这个函数讲这个对象的某个属性进行了修改。

而在React的函数式组件中,副作用函数需要使用Effect Hooks来进行操控。

如之前提过的例子

harmony
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import React, { useState, useEffect } from 'react';

function Example() {
const [count, setCount] = useState(0);

// Similar to componentDidMount and componentDidUpdate:
useEffect(() => {
// Update the document title using the browser API
document.title = `You clicked ${count} times`;
});

return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}

提示: 如果你熟悉 React class 的生命周期函数,你可以把 useEffect Hook 看做 componentDidMount,componentDidUpdate 和 componentWillUnmount 这三个函数的组合。

按照这个提示,可以理解为useEffect在每次render之后都会执行。

无需清除的副作用

无需清除的副作用通常指那些我们只想在React更新DOM之后运行的代码。诸如网络请求,手动更改DOM(上面的例子)。这些例子的特点就是我们执行完之后就可以忽略它们了。无需清除的意思也可以理解为不需要再去考虑消除我们的副作用对于代码的影响。比如我们手动更改了DOM之后不需要把DOM恢复原状。

需要清除的副作用

还有一些副作用是需要清除的,最常见的一个例子就是订阅外部数据源。假如我们在渲染之后订阅了一个频道,这个行为很明显是一个副作用。而在组件卸载之后,如果我们不进行任何操作,依旧订阅这个频道,那么就会面临着内存泄漏等糟糕的问题。

而使用useEffect来清除副作用的方式很简单,那就是返回一个函数来清除

harmony
1
2
3
4
5
6
7
8
9
10
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}

ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});

例如这个例子,我们在页面渲染之后,将Online状态置为true,表示在线。接着订阅Chat数据,这是会产生需要消除的副作用。最后我们返回了一个函数,该函数的作用是取消订阅。这样当组件卸载的时候就会执行该函数来取消订阅,从而消除副作用。

注意: 每次更新的时候都要运行Effect,包括其清除阶段。 这里举一个常见的bug来帮助我们理解。

harmony
1
2
3
4
5
6
7
8
9
10
11
12
13
componentDidMount() {
ChatAPI.subscribeToFriendStatus(
this.props.friend.id,
this.handleStatusChange
);
}

componentWillUnmount() {
ChatAPI.unsubscribeFromFriendStatus(
this.props.friend.id,
this.handleStatusChange
);
}

但是当组件已经显示在屏幕上时,friend prop 发生变化时会发生什么? 我们的组件将继续展示原来的好友状态。这是一个 bug。而且我们还会因为取消订阅时使用错误的好友 ID 导致内存泄露或崩溃的问题。

在class组件中,我们需要添加componentDidUpdate 来解决这个问题。

harmony
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
componentDidMount() {
ChatAPI.subscribeToFriendStatus(
this.props.friend.id,
this.handleStatusChange
);
}

componentDidUpdate(prevProps) {
// 取消订阅之前的 friend.id
ChatAPI.unsubscribeFromFriendStatus(
prevProps.friend.id,
this.handleStatusChange
);
// 订阅新的 friend.id
ChatAPI.subscribeToFriendStatus(
this.props.friend.id,
this.handleStatusChange
);
}

componentWillUnmount() {
ChatAPI.unsubscribeFromFriendStatus(
this.props.friend.id,
this.handleStatusChange
);
}

忘记正确地处理 componentDidUpdate 是 React 应用中常见的 bug 来源。

那么我们的Hook在每次更新的时候都会进行effect的运行与清除,这样就可以避免上面的bug。

侠名按照时间列出一个可能会产生的订阅与取消订阅操作的调用序列,来帮助我们理解。

harmony
1
2
3
4
5
6
7
8
9
10
11
12
13
// Mount with { friend: { id: 100 } } props
ChatAPI.subscribeToFriendStatus(100, handleStatusChange); // 运行第一个 effect

// Update with { friend: { id: 200 } } props
ChatAPI.unsubscribeFromFriendStatus(100, handleStatusChange); // 清除上一个 effect
ChatAPI.subscribeToFriendStatus(200, handleStatusChange); // 运行下一个 effect

// Update with { friend: { id: 300 } } props
ChatAPI.unsubscribeFromFriendStatus(200, handleStatusChange); // 清除上一个 effect
ChatAPI.subscribeToFriendStatus(300, handleStatusChange); // 运行下一个 effect

// Unmount
ChatAPI.unsubscribeFromFriendStatus(300, handleStatusChange); // 清除最后一个 effect

useEffect的第二个参数表示只有在第二个参数发生变化的时候才执行副作用。

harmony
1
2
3
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]); // 仅在 count 更改时更新

传入一个空数组表示只在挂载的时候执行一次。

感悟

useEffect 这个hooks同类组件的生命周期相比有一个很大的优势就是可以将代码按照业务逻辑分类思考。每个Hook代表一个逻辑,我们只需要关注这个逻辑在每个周期需要做什么。而类组件的生命周期管理方式强迫我们将关注点放在一个一个阶段,这让我们对于业务的管理变得很混乱。

这也是为什么函数式组件的代码显得十分简洁和易于维护的一大原因。

总结

React 函数式组件的特性以及用法远不止这些。后面的文档我目前还不太能看懂。上面的这些部分已经足够帮助我胜任手头的业务需求了。我决定先进行实践。等未来自己有经验了,再去回看React的函数式组件。

课否 · 回复功能开发实践(一): WebStorm的使用

寒假在家躺尸,不想学习只想玩,上学期快结束的时候忙于大作业和期末考试,博客也停滞更新了。

之前的色老师说的小程序课否也上线了,现在还挺多人用的,基本上是替代掉了查老师。

最近色老师让我做一个回复的功能。具体来讲就是在现在的老师评价的基础上,用户可以针对某一条评价进行回复,同时也可以针对回复进行回复。大概就是这样的需求。

于是我开了这个新坑,记录一下这次的开发过程。

工欲善其事,必先利其器。我之前一直在使用 vscode 作为 Web 开发的 ide,色老师向我推荐了 WebStorm,并且教我设置了一些常用的快捷键

快捷键的设置方式

在 File-> Settings 中点击 KeyMap, 右上角如下图所示,左侧可以按照功能进行搜索,右侧可以按照快捷键搜索

keyMap_SettingkeyMap_Setting

常用快捷键

  • 两次 shift 连按两次 shift 可以进行全局搜索
  • ctrl+B,ctrl+click 前往声明
  • ctrl+鼠标悬停,查看变量声明、注释等
  • ctrl+tab 切换 tab
  • 自己添加 alt+t 使用prettier进行代码格式化
  • ctrl+K commit
  • ctrl+y pull

scrapy框架爬虫入门,与培养方案抓取实践

最近色老师的项目想要其他学校的课程信息,于是让我去爬取别人的数据。
自己学习了一下scarpy,上手进行了简单的爬虫。

scrapy框架爬虫入门与培养方案抓取实践

现场爬一下蛤交的课程信息

https://github.com/YdreamW/sjtuSpider.git

scrapy文件结构

  • sjtuSpider
    • spiders
      • stju.py
      • getMajorUrl.py
    • items.py
    • pipelines.py

创建项目的过程

  • 安装框架

  • 创建项目

    • 新建项目。
      项目名可以用 …Spider
    1
    scrapy startproject <项目名>
    • 新建一个爬虫。
      爬虫名不能和项目名称一致
    1
    scrapy genspider <爬虫名> <域名限制>

写一个爬虫

  • 在此之前需要了解一下 xpath的简单语法

  • 推荐一个插件 xpath helper

  • getMajorUrl.py 的例子

  • 分析网站

    • 在chrome里找网页的逻辑
    • network 看请求
    • 可以先用 postman 模拟请求
  • 愉快的写代码,然后输出结果

  • 运行的爬虫的指令

1
scrapy crawl sjtu -o course1.json -s FEED_EXPORT_ENCODING=UTF-8

反思

了解了xpath,这个东西很简单就能入门,但是感觉要想熟练的分析页面还是需要一定的经验积累

最近学业压力比较重,爬虫只局限于了实践层面。
对于python的generator并没有理解的很透彻,以及python的面向对象都是一知半解,以后有机会好好搞清楚generator

​ 我印象中 es6 也有generator

DIP-HW6

实验六

一、实验目的和要求

  • 目的
    • 了解双边滤波的方法
  • 要求
    • 自己写C语言程序,实现所有操作,不调用其他库。

二、实验内容和原理

  • 内容

    • 读入图像
    • 给图像施加椒盐噪声
    • 对含有噪声的图像进行双边滤波
    • 输出图像
  • 原理

    • $I_p^{bf} = \frac{1}{W_p^{bf}}\sum_{q\in S} G_{\sigma_s}(|p-q|)G_{\sigma_r}(|Ip-Iq|Iq)$

      $W_P^{bf} = \sum_{q\in S} G_{\sigma_s}(|p-q|)G_{\sigma_r}(|I_p-I_q|)$

    • $w(i,j,k,l) = e^{(-\frac{(i-k)^2+(j-l)^2}{2\sigma_d^2}-\frac{|I(i,j)-I(k,l)|^2}{2\sigma_r^2})}$

      $I_D(i,j) = \frac{\sum_{k,l} I(k,l)w(i,j,k,l)}{\sum_{k,l}w(i,j,k,l)}$

    • 根据上述公式即可计算得到新的图像

三、源代码和分析

  • 源代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    #include<stdio.h>
    #include<stdlib.h>
    #include<math.h>
    #include<string.h>
    #include<time.h>
    #define WORD unsigned short
    #define DWORD unsigned int
    #define BYTE unsigned char
    int ph,pw,psize,lsize;
    double const sigma_r=100,sigma_d=100;
    typedef struct BMP_FILE_HEADER
    {
    WORD bfType;
    DWORD bfSize;
    WORD bfReserved1;
    WORD bfReserved2;
    DWORD bfOffBits;
    }BMPFILEHEADER;
    /* 位图信息头 */
    typedef struct BMP_INFO
    {
    DWORD biSize; // 信息头的大小
    DWORD biWidth; // 图像的宽度
    DWORD biHeight; // 图像的高度
    WORD biPlanes; // 图像的位面数
    WORD biBitCount; // 每个像素的位数
    DWORD biCompression; // 压缩类型
    DWORD biSizeImage; // 图像的大小,以字节为单位
    DWORD biXPelsPerMeter; // 水平分辨率
    DWORD biYPelsPerMeter; // 垂直分辨率
    DWORD biClrUsed; // 使用的色彩数
    DWORD biClrImportant; // 重要的颜色数
    } BMPINF; // 40 字节
    BMPFILEHEADER thisBmpFileHeader; // 定义一个 BMP 文件头的结构体
    BMPINF thisBmpInfo; // 定义一个 BMP 文件信息结构体
    void readFileHead(FILE *fp); //fp指向的文件的文件头读入到thisBMPFileHeader中
    void writeFileHead(FILE *fp); //thisBMPFileHeader写入到fp指向的文件的文件头中
    void BilateralFilter(BYTE *p,BYTE* pOUT,char *name);//均值滤波,结果存在pOUT中
    void OutPutImg(BYTE *pOUT,char *name);//输出图像
    void AddNoise_salt(BYTE* p, BYTE *pNoise);//增加盐噪声
    void AddNoise_pepper(BYTE* p, BYTE *pNoise);//增加椒噪声
    double getWeight(int i,int j,int tmpi,int tmpj,int I0,int I2);//计算权重
    int main()
    {
    FILE *fp; // 定义一个文件指针
    if((fp=fopen("in.bmp","rb"))==NULL)
    {
    printf("Can't open the file\n");
    return 0;
    }
    printf("open the file\n");
    readFileHead(fp);
    ph=thisBmpInfo.biHeight;//高度
    pw=thisBmpInfo.biWidth;//宽度
    psize=pw*ph*3;//图片信息字节总数
    lsize=pw*3;//每行信息字节总数
    BYTE* p=(BYTE*)malloc(psize);
    fread(p,psize,1,fp);
    fclose(fp);
    BYTE* pOUT=(BYTE*)malloc(psize);
    BYTE* pNoise=(BYTE*)malloc(psize);
    BilateralFilter(p,pOUT,"BilateralFiler_of_origin");
    AddNoise_salt(p,pNoise);
    BilateralFilter(pNoise,pOUT,"BilateralFiler_of_salt");
    AddNoise_pepper(p,pNoise);
    BilateralFilter(pNoise,pOUT,"BilateralFiler_of_pepper");
    return 0;
    }
    void OutPutImg(BYTE *pOUT,char *name)
    {
    char postfix[10]=".bmp",tmp[100];
    strcpy(tmp,name);
    strcat(tmp,postfix);
    FILE *fp=fopen(tmp,"wb");
    writeFileHead(fp);
    fwrite(pOUT,psize,1,fp);
    fclose(fp);
    printf("%s",name);
    printf(" finised!\n");
    }
    void readFileHead(FILE *fp)
    {
    fread(&thisBmpFileHeader.bfType,sizeof(thisBmpFileHeader.bfType),1,fp);
    fread(&thisBmpFileHeader.bfSize,sizeof(thisBmpFileHeader.bfSize),1,fp);
    fread(&thisBmpFileHeader.bfReserved1,sizeof(thisBmpFileHeader.bfReserved1),1,fp);
    fread(&thisBmpFileHeader.bfReserved2,sizeof(thisBmpFileHeader.bfReserved2),1,fp);
    fread(&thisBmpFileHeader.bfOffBits,sizeof(thisBmpFileHeader.bfOffBits),1,fp);
    fread(&thisBmpInfo.biSize, sizeof(thisBmpInfo.biSize), 1, fp);
    fread(&thisBmpInfo.biWidth, sizeof(thisBmpInfo.biWidth), 1, fp);
    fread(&thisBmpInfo.biHeight, sizeof(thisBmpInfo.biHeight), 1, fp);
    fread(&thisBmpInfo.biPlanes, sizeof(thisBmpInfo.biPlanes), 1, fp);
    fread(&thisBmpInfo.biBitCount, sizeof(thisBmpInfo.biBitCount), 1, fp);
    fread(&thisBmpInfo.biCompression, sizeof(thisBmpInfo.biCompression), 1, fp);
    fread(&thisBmpInfo.biSizeImage, sizeof(thisBmpInfo.biSizeImage), 1, fp);
    fread(&thisBmpInfo.biXPelsPerMeter, sizeof(thisBmpInfo.biXPelsPerMeter), 1, fp);
    fread(&thisBmpInfo.biYPelsPerMeter, sizeof(thisBmpInfo.biYPelsPerMeter), 1, fp);
    fread(&thisBmpInfo.biClrUsed, sizeof(thisBmpInfo.biClrUsed), 1, fp);
    fread(&thisBmpInfo.biClrImportant, sizeof(thisBmpInfo.biClrImportant), 1, fp);
    }
    void writeFileHead(FILE *fp)
    {
    // 写入位图文件头
    fwrite(&thisBmpFileHeader.bfType,sizeof(thisBmpFileHeader.bfType),1,fp);
    fwrite(&thisBmpFileHeader.bfSize,sizeof(thisBmpFileHeader.bfSize),1,fp);
    fwrite(&thisBmpFileHeader.bfReserved1,sizeof(thisBmpFileHeader.bfReserved1),1,fp);
    fwrite(&thisBmpFileHeader.bfReserved2,sizeof(thisBmpFileHeader.bfReserved2),1,fp);
    fwrite(&thisBmpFileHeader.bfOffBits,sizeof(thisBmpFileHeader.bfOffBits),1,fp);
    // 写入位图信息头
    fwrite(&thisBmpInfo.biSize, sizeof(thisBmpInfo.biSize), 1, fp);
    fwrite(&thisBmpInfo.biWidth, sizeof(thisBmpInfo.biWidth), 1, fp);
    fwrite(&thisBmpInfo.biHeight, sizeof(thisBmpInfo.biHeight), 1, fp);
    fwrite(&thisBmpInfo.biPlanes, sizeof(thisBmpInfo.biPlanes), 1, fp);
    fwrite(&thisBmpInfo.biBitCount, sizeof(thisBmpInfo.biBitCount), 1, fp);
    fwrite(&thisBmpInfo.biCompression, sizeof(thisBmpInfo.biCompression), 1, fp);
    fwrite(&thisBmpInfo.biSizeImage, sizeof(thisBmpInfo.biSizeImage), 1, fp);
    fwrite(&thisBmpInfo.biXPelsPerMeter, sizeof(thisBmpInfo.biXPelsPerMeter), 1, fp);
    fwrite(&thisBmpInfo.biYPelsPerMeter, sizeof(thisBmpInfo.biYPelsPerMeter), 1, fp);
    fwrite(&thisBmpInfo.biClrUsed, sizeof(thisBmpInfo.biClrUsed), 1, fp);
    fwrite(&thisBmpInfo.biClrImportant, sizeof(thisBmpInfo.biClrImportant), 1, fp);
    }
    void AddNoise_salt(BYTE* p, BYTE *pNoise)
    {
    srand(time(0));
    int *flag=(int *)malloc(ph*pw*sizeof(int));
    memset(flag,0,sizeof(int)*ph*pw);
    int num=ph*pw*0.01;
    int i,j,tmp,_;
    for(i=0;i<ph;i++)
    for(j=0;j<pw;j++)
    {
    tmp=i*lsize+j*3;
    for(_=0;_<3;_++)
    pNoise[tmp+_]=p[tmp+_];
    }
    for(i=0;i<num;i++)
    {
    int tmpi=rand()%ph,
    tmpj=rand()%pw;
    while(flag[tmpi*pw+tmpj])
    {
    tmpi=rand()%ph;
    tmpj=rand()%pw;
    }
    flag[tmpi*pw+tmpj]=1;
    for(_=0;_<3;_++)
    {
    pNoise[tmpi*lsize+tmpj*3+_]=255;
    }
    }
    OutPutImg(pNoise,"Salt_Noise");
    }
    void AddNoise_pepper(BYTE* p, BYTE *pNoise)
    {
    srand(time(0));
    int *flag=(int *)malloc(ph*pw*sizeof(int));
    memset(flag,0,sizeof(int)*ph*pw);
    int num=ph*pw*0.01;
    int i,j,tmp,_;
    for(i=0;i<ph;i++)
    for(j=0;j<pw;j++)
    {
    tmp=i*lsize+j*3;
    for(_=0;_<3;_++)
    pNoise[tmp+_]=p[tmp+_];
    }
    for(i=0;i<num;i++)
    {
    int tmpi=rand()%ph,
    tmpj=rand()%pw;
    while(flag[tmpi*pw+tmpj])
    {
    tmpi=rand()%ph;
    tmpj=rand()%pw;
    }
    flag[tmpi*pw+tmpj]=1;
    for(_=0;_<3;_++)
    {
    pNoise[tmpi*lsize+tmpj*3+_]=0;
    }
    }
    OutPutImg(pNoise,"Pepper_Noise");
    }
    void BilateralFilter(BYTE *p,BYTE* pOUT,char *name)
    {
    int i,j,tmp,_,o;
    int fx[49]={0},fy[49]={0};
    for(i=0,_=0;i<7;i++)
    for(j=0;j<7;j++,_++)
    {
    fx[_]=i-3;
    fy[_]=j-3;
    }
    for(i=0;i<ph;i++)
    {
    for(j=0;j<pw;j++)
    {
    tmp=i*lsize+j*3;
    for(_=0;_<3;_++)
    {
    int flag=0;
    double G=0,W=0;
    for(o=0;o<49;o++)
    {
    int tmpi=i+fx[o],tmpj=j+fy[o];
    if(tmpi>=0&&tmpi<ph&&tmpj>=0&&tmpj<pw)
    {
    int t=tmpi*lsize+tmpj*3;
    double w=getWeight(i,j,tmpi,tmpj,p[tmp+_],p[t+_]);
    G+=p[t+_]*w;
    W+=w;
    }
    else flag=1;
    }
    if(!flag)
    pOUT[tmp+_]=(G/W > 255) ? 255 : G/W;
    else
    pOUT[tmp+_]=p[tmp+_];
    }
    }
    }
    OutPutImg(pOUT,name);
    }
    double getWeight(i,j,tmpi,tmpj,I0,I2)
    {
    double d=((double)((i-tmpi)*(i-tmpi)+(j-tmpj)*(j-tmpj)))/(2*sigma_d*sigma_d);
    double r=((double)((I0-I2)*(I0-I2)))/(2*sigma_r*sigma_r);
    return exp(-1*d-r);
    }
  • 分析

    • 双边滤波函数

      利用fx,fy两个数组作为坐标偏移函数

      首先通过一个循环为坐标偏移函数赋值

      然后枚举$77$方格中的像素点,利用getWeight* 函数计算得到相应的权重,并记录累加的值。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    void BilateralFilter(BYTE *p,BYTE* pOUT,char *name)
    {
    int i,j,tmp,_,o;
    int fx[49]={0},fy[49]={0};
    for(i=0,_=0;i<7;i++)
    for(j=0;j<7;j++,_++)
    {
    fx[_]=i-3;
    fy[_]=j-3;
    }
    for(i=0;i<ph;i++)
    {
    for(j=0;j<pw;j++)
    {
    tmp=i*lsize+j*3;
    for(_=0;_<3;_++)
    {
    int flag=0;
    double G=0,W=0;
    for(o=0;o<49;o++)
    {
    int tmpi=i+fx[o],tmpj=j+fy[o];
    if(tmpi>=0&&tmpi<ph&&tmpj>=0&&tmpj<pw)
    {
    int t=tmpi*lsize+tmpj*3;
    double w=getWeight(i,j,tmpi,tmpj,p[tmp+_],p[t+_]);
    G+=p[t+_]*w;
    W+=w;
    }
    else flag=1;
    }
    if(!flag)
    pOUT[tmp+_]=(G/W > 255) ? 255 : G/W;
    else
    pOUT[tmp+_]=p[tmp+_];
    }
    }
    }
    OutPutImg(pOUT,name);
    }
    • 权重计算

      根据公式,直接计算各个像素点的权重

      1
      2
      3
      4
      5
      6
      double getWeight(i,j,tmpi,tmpj,I0,I2)
      {
      double d=((double)((i-tmpi)*(i-tmpi)+(j-tmpj)*(j-tmpj)))/(2*sigma_d*sigma_d);
      double r=((double)((I0-I2)*(I0-I2)))/(2*sigma_r*sigma_r);
      return exp(-1*d-r);
      }

心得体会

  • 实验结果

    对原图的双边滤波

    BilateralFiler_of_originBilateralFiler_of_origin

    对原图施加盐噪声

    Salt_NoiseSalt_Noise

    对噪声干扰后的图像进行双边滤波

    BilateralFiler_of_saltBilateralFiler_of_salt

  • 心得

    本次实验的代码和上次实验大体相似,区别仅在于权重的计算变得复杂一些。继续沿用上次实验坐标偏移的做法,大大简化了代码的编写难度

    通过调整两个 $\sigma$ 的值,我们可以看到lena 的模糊程度会发生变化。

    通过这次实验,我对于高斯滤波、双边滤波有了更深的理解。双边滤波综合距离和像素变化两者影响的思想及方法值得我们深思。

多译 · 更新日志页面开发(五) 简单使用 eggjs和MongoDB

前些天在前端用Mock数据来进行开发,最近把后端给写了一下。

同样在别人的项目基础上开发确实是很容易的。

1、路由

在router中增加两个router接口

1
2
3
4
5
6
7
8
9
// 获取更新日志内容
router.get('/software/changelog', controller.software.getChangelog);
// 提交更新日志
router.post(
'/admin/uploadchangelog',
app.middleware.jwtAuth(),
app.middleware.checkAdmin(),
controller.software.uploadChangelog
);

2、controller

controller负责针对不同的URL请求进行数据处理

1
2
3
4
5
6
7
public async getChangelog() {
const res = await this.ctx.model.Software.find().sort({ ['version']: -1 });
this.ctx.body = {
success: true,
data: res,
};
}

controller中的函数应当是async函数,而其中对于数据库的操作应当用await,因为数据库的query是异步的。

3、Mongoose

Mongoose是js对于MongoDB操作的库。它通过在js中定义schema来定义数据库的结构。并且它和数据库是同步的。

不用像MySQL那样create table了,这就是新世纪的数据库哈哈哈哈

另外Mongoose的query好像也很复杂,这些东西等以后学了数据库再来慢慢学吧

4、中间件

关注router中的第二个接口,我们在path和controller方法中间增加了两个函数,其作用主要是进行鉴权。这里用到了Eggjs的中间件。

我们只需要在middleware中定义一个中间件,即可在router中使用

1
2
3
4
5
6
7
8
9
10
11
12
13
export default () => async (ctx: Context, next) => {
const user = await ctx.helper.verifyAdmin();
const {uid}=JSON.parse(JSON.stringify(user));
const id = await ctx.model.User.findOne( {_id:uid} );
if(id&&id.admin){
ctx.state.user = user;
await next();
}
else
{
ctx.throw(401, "not admin");
}
};

miniproject也就此结束了。这个项目实现的效果虽然很简单,但我还是很有成就感的,毕竟我也算是学会了React哈哈哈哈

最近这段时间有点忙。这个学期简直致命,数逻、oop各种大作业接踵而至。我要开始写大作业和复习了。

DIP-HW5

实验五

一、实验目的和要求

  • 目的
    • 了解连续和离散的函数卷积的原理
    • 了解噪声对图片的影响,并掌握简单的为图像增加噪声的方法。
    • 了解滤波函数,并掌握均值滤波和中值滤波的方法。
    • 掌握拉普拉斯算子增强图像的方法。
  • 要求
    • 自己写C语言程序,实现所有操作,不调用其他库。

二、实验内容和原理

  • 内容:

    • 对图像添加盐椒噪声。
    • 对图像进行滤波处理,去除噪声。
    • 利用拉普拉斯算子对图像进行增强。
  • 原理:

    • 连续函数的卷积

      $g(x)=f(x)*h(x)=\int_{-\infty}^{\infty} f(x)*h(t-x)dt$

    • 卷积的性质

      $f(x)*h(x)=h(x)*f(x)$

      $f*(g+h)=fg+fh$

      $(fg)h=f(gh)$

    • 离散函数的卷积

      $g(x)=f(x)*h(x)=\frac{1}{M}\sum_{t=0}^{M-1}f(t)*h(x-t)$

    • 卷积等价于计算区域内像素点值的加权和

      image-20191201001543946image-20191201001543946

    • 滤波

      根据公式 $g(x,y)=\sum_{s=-a}^{a}\sum_{t=-b}^{b}w(s,t)*f(x+s,x+t)$

      得到新的图像 g

      不同的滤波方法的区别就在于w(s,t)的形式,可以用一个矩阵来表示。

      如均值滤波 $\frac{1}{9}\begin{bmatrix}1,1,1\1,1,1\1,1,1\end{bmatrix}$

    • 拉普拉斯算子增强

      $\frac{\part^2f}{\part x^2} = f(x+1,y) + f(x-1,y)-2*f(x,y)$

      $\frac{\part^2f}{\part y^2} = f(x,y+1) + f(x,y-1)-2*f(x,y)$

      $\bigtriangledown ^2f = [ f(x+1,y) + f(x-1,y)+ f(x,y+1) + f(x,y-1)]-4*f(x,y)$

      用矩阵表示:$\begin{bmatrix}0,1,0\1,-4,1\0,1,0\end{bmatrix}$

      将对角线的元素也考虑进去: $\begin{bmatrix}1,1,1\1,-8,1\1,1,1\end{bmatrix}$

      根据公式 $g(x,y)=f(x,y)-\bigtriangledown ^2f(x,y)$ 合并得到增强后的图像

    • 椒盐噪声

      椒盐噪声的原理是在图像中随机选取一些像素点,将其变成白色或者黑色,对应的分别是盐噪声和椒噪声。

三、源代码和分析

  • 源代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    274
    275
    276
    277
    278
    279
    280
    281
    282
    283
    284
    285
    286
    287
    288
    289
    290
    291
    292
    293
    294
    295
    296
    297
    298
    299
    300
    301
    302
    303
    304
    305
    306
    307
    #include<stdio.h>
    #include<stdlib.h>
    #include<math.h>
    #include<string.h>
    #include<time.h>
    #define WORD unsigned short
    #define DWORD unsigned int
    #define BYTE unsigned char
    int ph,pw,psize,lsize;
    typedef struct BMP_FILE_HEADER
    {
    WORD bfType;
    DWORD bfSize;
    WORD bfReserved1;
    WORD bfReserved2;
    DWORD bfOffBits;
    }BMPFILEHEADER;
    /* 位图信息头 */
    typedef struct BMP_INFO
    {
    DWORD biSize; // 信息头的大小
    DWORD biWidth; // 图像的宽度
    DWORD biHeight; // 图像的高度
    WORD biPlanes; // 图像的位面数
    WORD biBitCount; // 每个像素的位数
    DWORD biCompression; // 压缩类型
    DWORD biSizeImage; // 图像的大小,以字节为单位
    DWORD biXPelsPerMeter; // 水平分辨率
    DWORD biYPelsPerMeter; // 垂直分辨率
    DWORD biClrUsed; // 使用的色彩数
    DWORD biClrImportant; // 重要的颜色数
    } BMPINF; // 40 字节
    BMPFILEHEADER thisBmpFileHeader; // 定义一个 BMP 文件头的结构体
    BMPINF thisBmpInfo; // 定义一个 BMP 文件信息结构体
    void readFileHead(FILE *fp); //fp指向的文件的文件头读入到thisBMPFileHeader中
    void writeFileHead(FILE *fp); //thisBMPFileHeader写入到fp指向的文件的文件头中
    void meanFilter(BYTE *p,BYTE* pOUT,char *name);
    void medianFilter(BYTE *p,BYTE* pOUT,char *name);
    void Laplacian(BYTE *p,char *name);
    void OutPutImg(BYTE *pOUT,char *name);
    void AddNoise_salt(BYTE* p, BYTE *pNoise);
    void AddNoise_pepper(BYTE* p, BYTE *pNoise);
    int cmp_int(const void* _a , const void* _b);
    int main()
    {
    FILE *fp; // 定义一个文件指针
    if((fp=fopen("in.bmp","rb"))==NULL)
    {
    printf("Can't open the file\n");
    return 0;
    }
    printf("open the file\n");
    readFileHead(fp);
    ph=thisBmpInfo.biHeight;//高度
    pw=thisBmpInfo.biWidth;//宽度
    psize=pw*ph*3;//图片信息字节总数
    lsize=pw*3;//每行信息字节总数
    BYTE* p=(BYTE*)malloc(psize);
    fread(p,psize,1,fp);
    fclose(fp);
    BYTE* pOUT=(BYTE*)malloc(psize);
    meanFilter(p,pOUT,"meanFiler_of_origin");
    Laplacian(p,"Laplacian_of_origin");
    BYTE* pNoise=(BYTE*)malloc(psize);
    AddNoise_salt(p,pNoise);
    meanFilter(pNoise,pOUT,"meanFiler_of_salt");
    Laplacian(pOUT,"Laplacian_of_meanFiler_of_salt");
    medianFilter(pNoise,pOUT,"medianFiler_of_salt");
    Laplacian(pOUT,"Laplacian_of_medianFilter_of_salt");
    AddNoise_pepper(p,pNoise);
    meanFilter(pNoise,pOUT,"meanFiler_of_pepper");
    Laplacian(pOUT,"Laplacian_of_meanFiler_of_pepper");
    medianFilter(pNoise,pOUT,"medianFiler_of_pepper");
    Laplacian(pOUT,"Laplacian_of_medianFilter_of_pepper");
    return 0;
    }
    void meanFilter(BYTE *p,BYTE* pOUT,char *name)
    {
    int i,j,tmp,_,o;
    int fx[9]={-1,-1,-1,0,0,0,1,1,1},fy[9]={-1,0,1,-1,0,1,-1,0,1};
    for(i=0;i<ph;i++)
    {
    for(j=0;j<pw;j++)
    {
    tmp=i*lsize+j*3;
    for(_=0;_<3;_++)
    {
    int flag=0,value=0;
    for(o=0;o<9;o++)
    {
    int tmpi=i+fx[o],tmpj=j+fy[o];
    if(tmpi>=0&&tmpi<ph&&tmpj>=0&&tmpj<pw)
    {
    int t=tmpi*lsize+tmpj*3;
    value+=p[t+_];
    }
    else flag=1;
    }
    if(!flag)
    pOUT[tmp+_]=value/9;
    else
    pOUT[tmp+_]=p[tmp+_];
    }
    }
    }
    OutPutImg(pOUT,name);
    }
    void Laplacian(BYTE *p,char *name)
    {
    BYTE* pOUT=(BYTE*)malloc(psize);
    int i,j,tmp,_,o;
    int fx[8]={-1,-1,-1,0,0,1,1,1},fy[8]={-1,0,1,-1,1,-1,0,1};
    for(i=0;i<ph;i++)
    {
    for(j=0;j<pw;j++)
    {
    tmp=i*lsize+j*3;
    for(_=0;_<3;_++)
    {
    int flag=0,value=0;
    for(o=0;o<8;o++)
    {
    int tmpi=i+fx[o],tmpj=j+fy[o];
    if(tmpi>=0&&tmpi<ph&&tmpj>=0&&tmpj<pw)
    {
    int t=tmpi*lsize+tmpj*3;
    value+=p[t+_];
    }
    else flag=1;
    }
    if(!flag)
    {
    int nV=p[tmp+_]-(value-8*p[tmp+_]);
    if(nV<0)
    nV=0;
    if(nV>255)
    nV=255;
    pOUT[tmp+_]=nV;

    }
    else
    pOUT[tmp+_]=p[tmp+_];

    }
    }
    }
    OutPutImg(pOUT,name);
    }
    void OutPutImg(BYTE *pOUT,char *name)
    {
    char postfix[10]=".bmp",tmp[100];
    strcpy(tmp,name);
    strcat(tmp,postfix);
    FILE *fp=fopen(tmp,"wb");
    writeFileHead(fp);
    fwrite(pOUT,psize,1,fp);
    fclose(fp);
    printf("%s",name);
    printf(" finised!\n");
    }
    void readFileHead(FILE *fp)
    {
    fread(&thisBmpFileHeader.bfType,sizeof(thisBmpFileHeader.bfType),1,fp);
    fread(&thisBmpFileHeader.bfSize,sizeof(thisBmpFileHeader.bfSize),1,fp);
    fread(&thisBmpFileHeader.bfReserved1,sizeof(thisBmpFileHeader.bfReserved1),1,fp);
    fread(&thisBmpFileHeader.bfReserved2,sizeof(thisBmpFileHeader.bfReserved2),1,fp);
    fread(&thisBmpFileHeader.bfOffBits,sizeof(thisBmpFileHeader.bfOffBits),1,fp);
    fread(&thisBmpInfo.biSize, sizeof(thisBmpInfo.biSize), 1, fp);
    fread(&thisBmpInfo.biWidth, sizeof(thisBmpInfo.biWidth), 1, fp);
    fread(&thisBmpInfo.biHeight, sizeof(thisBmpInfo.biHeight), 1, fp);
    fread(&thisBmpInfo.biPlanes, sizeof(thisBmpInfo.biPlanes), 1, fp);
    fread(&thisBmpInfo.biBitCount, sizeof(thisBmpInfo.biBitCount), 1, fp);
    fread(&thisBmpInfo.biCompression, sizeof(thisBmpInfo.biCompression), 1, fp);
    fread(&thisBmpInfo.biSizeImage, sizeof(thisBmpInfo.biSizeImage), 1, fp);
    fread(&thisBmpInfo.biXPelsPerMeter, sizeof(thisBmpInfo.biXPelsPerMeter), 1, fp);
    fread(&thisBmpInfo.biYPelsPerMeter, sizeof(thisBmpInfo.biYPelsPerMeter), 1, fp);
    fread(&thisBmpInfo.biClrUsed, sizeof(thisBmpInfo.biClrUsed), 1, fp);
    fread(&thisBmpInfo.biClrImportant, sizeof(thisBmpInfo.biClrImportant), 1, fp);
    }
    void writeFileHead(FILE *fp)
    {
    // 写入位图文件头
    fwrite(&thisBmpFileHeader.bfType,sizeof(thisBmpFileHeader.bfType),1,fp);
    fwrite(&thisBmpFileHeader.bfSize,sizeof(thisBmpFileHeader.bfSize),1,fp);
    fwrite(&thisBmpFileHeader.bfReserved1,sizeof(thisBmpFileHeader.bfReserved1),1,fp);
    fwrite(&thisBmpFileHeader.bfReserved2,sizeof(thisBmpFileHeader.bfReserved2),1,fp);
    fwrite(&thisBmpFileHeader.bfOffBits,sizeof(thisBmpFileHeader.bfOffBits),1,fp);
    // 写入位图信息头
    fwrite(&thisBmpInfo.biSize, sizeof(thisBmpInfo.biSize), 1, fp);
    fwrite(&thisBmpInfo.biWidth, sizeof(thisBmpInfo.biWidth), 1, fp);
    fwrite(&thisBmpInfo.biHeight, sizeof(thisBmpInfo.biHeight), 1, fp);
    fwrite(&thisBmpInfo.biPlanes, sizeof(thisBmpInfo.biPlanes), 1, fp);
    fwrite(&thisBmpInfo.biBitCount, sizeof(thisBmpInfo.biBitCount), 1, fp);
    fwrite(&thisBmpInfo.biCompression, sizeof(thisBmpInfo.biCompression), 1, fp);
    fwrite(&thisBmpInfo.biSizeImage, sizeof(thisBmpInfo.biSizeImage), 1, fp);
    fwrite(&thisBmpInfo.biXPelsPerMeter, sizeof(thisBmpInfo.biXPelsPerMeter), 1, fp);
    fwrite(&thisBmpInfo.biYPelsPerMeter, sizeof(thisBmpInfo.biYPelsPerMeter), 1, fp);
    fwrite(&thisBmpInfo.biClrUsed, sizeof(thisBmpInfo.biClrUsed), 1, fp);
    fwrite(&thisBmpInfo.biClrImportant, sizeof(thisBmpInfo.biClrImportant), 1, fp);
    }
    void AddNoise_salt(BYTE* p, BYTE *pNoise)
    {
    srand(time(0));
    int *flag=(int *)malloc(ph*pw*sizeof(int));
    memset(flag,0,sizeof(int)*ph*pw);
    int num=ph*pw*0.01;
    int i,j,tmp,_;
    for(i=0;i<ph;i++)
    for(j=0;j<pw;j++)
    {
    tmp=i*lsize+j*3;
    for(_=0;_<3;_++)
    pNoise[tmp+_]=p[tmp+_];
    }
    for(i=0;i<num;i++)
    {
    int tmpi=rand()%ph,
    tmpj=rand()%pw;
    while(flag[tmpi*pw+tmpj])
    {
    tmpi=rand()%ph;
    tmpj=rand()%pw;
    }
    flag[tmpi*pw+tmpj]=1;
    for(_=0;_<3;_++)
    {
    pNoise[tmpi*lsize+tmpj*3+_]=255;
    }
    }
    OutPutImg(pNoise,"Salt_Noise");
    }
    void AddNoise_pepper(BYTE* p, BYTE *pNoise)
    {
    srand(time(0));
    int *flag=(int *)malloc(ph*pw*sizeof(int));
    memset(flag,0,sizeof(int)*ph*pw);
    int num=ph*pw*0.01;
    int i,j,tmp,_;
    for(i=0;i<ph;i++)
    for(j=0;j<pw;j++)
    {
    tmp=i*lsize+j*3;
    for(_=0;_<3;_++)
    pNoise[tmp+_]=p[tmp+_];
    }
    for(i=0;i<num;i++)
    {
    int tmpi=rand()%ph,
    tmpj=rand()%pw;
    while(flag[tmpi*pw+tmpj])
    {
    tmpi=rand()%ph;
    tmpj=rand()%pw;
    }
    flag[tmpi*pw+tmpj]=1;
    for(_=0;_<3;_++)
    {
    pNoise[tmpi*lsize+tmpj*3+_]=0;
    }
    }
    OutPutImg(pNoise,"Pepper_Noise");
    }
    int cmp_int(const void* _a , const void* _b)
    {
    int* a = (int*)_a;
    int* b = (int*)_b;
    return *a - *b;
    }
    void medianFilter(BYTE *p,BYTE* pOUT,char *name)
    {
    int i,j,tmp,_,o;
    int fx[9]={-1,-1,-1,0,0,0,1,1,1},fy[9]={-1,0,1,-1,0,1,-1,0,1},value[9];
    for(i=0;i<ph;i++)
    {
    for(j=0;j<pw;j++)
    {
    tmp=i*lsize+j*3;
    for(_=0;_<3;_++)
    {
    int flag=0;
    for(o=0;o<9;o++)
    {
    int tmpi=i+fx[o],tmpj=j+fy[o];
    if(tmpi>=0&&tmpi<ph&&tmpj>=0&&tmpj<pw)
    {
    int t=tmpi*lsize+tmpj*3;
    value[o]=p[t+_];
    }
    else
    {
    flag=1;
    break;
    }
    }
    if(!flag)
    {
    qsort(value,9,sizeof(int),cmp_int);
    pOUT[tmp+_]=value[4];
    }
    else
    pOUT[tmp+_]=p[tmp+_];
    }
    }
    }
    OutPutImg(pOUT,name);
    }

  • 分析

    • 函数列表及功能
    1
    2
    3
    4
    5
    6
    7
    8
    9
    void readFileHead(FILE *fp);   //fp指向的文件的文件头读入到thisBMPFileHeader中
    void writeFileHead(FILE *fp); //thisBMPFileHeader写入到fp指向的文件的文件头中
    void meanFilter(BYTE *p,BYTE* pOUT,char *name);//均值滤波,结果存入pOUT中
    void medianFilter(BYTE *p,BYTE* pOUT,char *name);//中值滤波
    void Laplacian(BYTE *p,char *name);//拉普拉斯算子增强
    void OutPutImg(BYTE *pOUT,char *name);//输出图像
    void AddNoise_salt(BYTE* p, BYTE *pNoise);//增加盐噪声
    void AddNoise_pepper(BYTE* p, BYTE *pNoise);//增加椒噪声
    int cmp_int(const void* _a , const void* _b);//用于排序的比较函数
    • 均值滤波

      对于每一个像素点,计算其周围3*3网格中像素点颜色的均值,作为新图像的值。

      构造坐标偏移数组fx,fy。利用这两个数组进行像素点枚举。RGB三个轨道分别进行计算。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    void meanFilter(BYTE *p,BYTE* pOUT,char *name)
    {
    int i,j,tmp,_,o;
    int fx[9]={-1,-1,-1,0,0,0,1,1,1},fy[9]={-1,0,1,-1,0,1,-1,0,1};
    for(i=0;i<ph;i++)
    {
    for(j=0;j<pw;j++)
    {
    tmp=i*lsize+j*3;
    for(_=0;_<3;_++)
    {
    int flag=0,value=0;
    for(o=0;o<9;o++)
    {
    int tmpi=i+fx[o],tmpj=j+fy[o];
    if(tmpi>=0&&tmpi<ph&&tmpj>=0&&tmpj<pw)
    {
    int t=tmpi*lsize+tmpj*3;
    value+=p[t+_];
    }
    else flag=1;
    }
    if(!flag)
    pOUT[tmp+_]=value/9;
    else
    pOUT[tmp+_]=p[tmp+_];
    }
    }
    }
    OutPutImg(pOUT,name);
    }
    • 中值滤波

      对于每一个像素点,找到其周围3*3网格中像素点颜色的中值,作为新图像的值。

      具体分析同上

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    void medianFilter(BYTE *p,BYTE* pOUT,char *name)
    {
    int i,j,tmp,_,o;
    int fx[9]={-1,-1,-1,0,0,0,1,1,1},fy[9]={-1,0,1,-1,0,1,-1,0,1},value[9];
    for(i=0;i<ph;i++)
    {
    for(j=0;j<pw;j++)
    {
    tmp=i*lsize+j*3;
    for(_=0;_<3;_++)
    {
    int flag=0;
    for(o=0;o<9;o++)
    {
    int tmpi=i+fx[o],tmpj=j+fy[o];
    if(tmpi>=0&&tmpi<ph&&tmpj>=0&&tmpj<pw)
    {
    int t=tmpi*lsize+tmpj*3;
    value[o]=p[t+_];
    }
    else
    {
    flag=1;
    break;
    }
    }
    if(!flag)
    {
    qsort(value,9,sizeof(int),cmp_int);
    pOUT[tmp+_]=value[4];
    }
    else
    pOUT[tmp+_]=p[tmp+_];
    }
    }
    }
    OutPutImg(pOUT,name);
    }
    • 拉普拉斯算子

      根据拉普拉斯算子,计算得到图片锐化的值,并将其和原图合并。这里一起进行了这布操作。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    void Laplacian(BYTE *p,char *name)
    {
    BYTE* pOUT=(BYTE*)malloc(psize);
    int i,j,tmp,_,o;
    int fx[8]={-1,-1,-1,0,0,1,1,1},fy[8]={-1,0,1,-1,1,-1,0,1};
    for(i=0;i<ph;i++)
    {
    for(j=0;j<pw;j++)
    {
    tmp=i*lsize+j*3;
    for(_=0;_<3;_++)
    {
    int flag=0,value=0;
    for(o=0;o<8;o++)
    {
    int tmpi=i+fx[o],tmpj=j+fy[o];
    if(tmpi>=0&&tmpi<ph&&tmpj>=0&&tmpj<pw)
    {
    int t=tmpi*lsize+tmpj*3;
    value+=p[t+_];
    }
    else flag=1;
    }
    if(!flag)
    {
    int nV=p[tmp+_]-(value-8*p[tmp+_]);
    if(nV<0)
    nV=0;
    if(nV>255)
    nV=255;
    pOUT[tmp+_]=nV;

    }
    else
    pOUT[tmp+_]=p[tmp+_];

    }
    }
    }
    OutPutImg(pOUT,name);
    }
  • 实验结果展示

    盐噪声

Salt_NoiseSalt_Noise

盐噪声均值滤波

meanFiler_of_saltmeanFiler_of_salt

均值滤波的拉普拉斯增强

Laplacian_of_meanFiler_of_saltLaplacian_of_meanFiler_of_salt

中值滤波

medianFiler_of_saltmedianFiler_of_salt

中值滤波的拉普拉斯增强

Laplacian_of_medianFilter_of_saltLaplacian_of_medianFilter_of_salt

四、心得体会

本次实验主要学习了函数卷积、均值滤波和中值滤波的方法。并且了解了拉普拉斯算子对图像的增强。

这次实验的代码相较上次作业比较简单,在写代码过程中没有出现严重的bug。值得注意的一个点是,在每次计算得到像素点的颜色的值的时候,如果其值不在[0,255]时,应将其值限制在[0,255]上。

另外,中值滤波和均值滤波对于盐椒噪声消除的效果是不同的。由于盐椒噪声的值相对极端,其对均值的影响较大,故采用中值滤波的话,可以良好的消除极端点的影响。

拉普拉斯算子能够锐化图像,增强图像效果十分明显。其对于极端数据也非常敏感。如果滤波效果很差的话,噪声也会得到明显加强

多译 · 更新日志页面开发(四) 在实践中使用Umi+Dva+Ant Design

之前色老师给我们线上开过一个小会,会议内容是带领我们浏览一下多译的代码,以及umi框架的架构。

这几天我完成了自己MiniProject的前端部分,在这里记录一下这一套框架的简单使用情况。

1、路由的配置

在config目录下,有一个router.ts,其中定义了路由信息。

我照猫画虎,在已有的页面后面增加了changelog界面。

关于这部分,我其实并不能理解其路由的结构,尤其是其嵌套的形式。TODO: 以后有时间好好读一下umi的文档。(最近确实比较忙,还是先把项目做出来再说吧)

2、源代码的结构

源代码的目录下主要由几部分构成

1
2
3
4
5
components   ------一些组件
layouts
models ------model的定义,或者说Redux的定义
pages ------页面
services ------提供给model的一些方法,诸如http请求等

说来惭愧,layouts部分色老师也讲过,但是我好巧不巧当时走神了。后来看了一下也没太能看懂…..

TODO: 抽空了解一下 layouts的作用

3、model 结构

model由一个 connect.d.ts 和 若干个ts文件组成。其中前者像是model的入口文件,其他ts文件则分别定义了若干个store

这一点在之前学习Redux的时候,印象中阮一峰老师有提到过大的项目中store通常会分开管理,然后Redux提供了将它们合并的方法。

我在model中新建了 changelog.ts 文件,用来定义更新日志的store,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
import { DvaModel, Effect } from '@/models/connect';
import { getChangelog, uploadChangelog } from '@/services/changelog';

export interface LogItem {
url: {
win: string;
mac: string;
};
version: string;
createdAt: Date;
updatedAt: Date;
description: string;
}

export interface ChangelogModelState {
data: LogItem[];
IsSuccess: boolean;
messege: String;
}

export interface ChangelogModel extends DvaModel<ChangelogModelState> {}

const Model: ChangelogModel = {
state: {
data: [],
IsSuccess: false,
messege: '',
},
reducers: {
save(state, { payload }) {
return {
...state,
...payload,
};
},
},
effects: {
*fetchData(_, { call, put }) {
const res = yield call(getChangelog);
const { success, data } = res;
if (success) {
yield put({
type: 'save',
payload: { data },
});
}
},
*uploadData({ payload }, { call, put }) {
let data = payload;
try {
const res = yield call(uploadChangelog, data);
if (res && res.success) {
yield put({
type: 'save',
payload: { IsSuccess: true },
});
}
} catch (e) {
const { response } = e;
const { status } = response;
if (status === 409) {
yield put({
type: 'save',
payload: { messege: '版本已存在' },
});
}
if (status === 401) {
yield put({
type: 'save',
payload: { messege: '没有权限提交' },
});
}
}
},
},
};
export default Model;

首先需要定义store中state的接口,如代码中的 ChangelogModelState,这里的命名我参照了色老师其他store的命名,以此来规范代码中的变量名。

接着定义了model的接口,ChangelogModel,这个接口继承了 DvaModel,并且将自己定义的state的类型作为泛型实例化。

最后利用继承来的类型来定义Model,并且完成state的初始化。

reducers 和 effects 共同定义了action的类型,其中effects定义了具有副作用的action。包括那些需要进行网络请求的action。 reduces则是一些直接更新state的纯函数。

在effects中,我们可以传递call put 方法,其中call方法可以用来调用request,put则可以通过action来执行reducer中的方法。

一个值得注意的点: 在reducer和effects中参数中的payload要用解构赋值, fetchData({payload},_) 这样payload才是我们调用dispatch时传入的payload。(最开始没有用解构赋值,然后发现获取不到数据,查bug查了好久,哭)

接着在connect.d.ts中添加changelog的入口。(这部分代码由于涉及色老师写的,因此不方便贴出来)

TODO: 抽空去看一下 .d.ts 这个后缀的文件名和 .ts 之间的区别

4、页面(组件)

4.1 组件结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
@connect(({ changelog }: ConnectState) => ({
data: changelog.data,
}))
export default class Changelog extends Component<DispatchProps> {
componentDidMount(): void {
this.props.dispatch({ type: 'changelog/fetchData' });
}
render() {
return (
<Fragment>
<Row>
<Col span="1.5"></Col>
<Col span="22.5">
<h1>更新日志</h1>
</Col>
</Row>
<Timeline>{this.myTimelineItem()}</Timeline>
</Fragment>
);
}
myTimelineItem() {
return this.props.data.map((item, index) => {
return (
<Timeline.Item>
<Row>
<Col span="1"></Col>
<Col span="23">
<Typography>
<Title level={3}>{item.version}</Title>
{this.myDate(item.createdAt)}
<Paragraph>
<ul>{this.myLi(item.description)}</ul>
</Paragraph>
</Typography>
</Col>
</Row>
</Timeline.Item>
);
});
}
myLi(description: String) {
let arr = description.split('\n');
let sta = [];
//去除空字符串
for (let i = 0; i < arr.length; i++) if (arr[i] === '') sta.push(i);
for (let i = 0; i < sta.length; i++) arr.splice(sta[i], 1);
return arr.map((item, index) => <li>{item}</li>);
}
myDate(createdAt) {
if (createdAt) {
let arr = createdAt.split('T');
return <Tag>{arr[0]}</Tag>;
}
}
}

首先要先 connect,这里是Dva的语法

接着就是组件类的定义。didMount 生命周期中发送 action: fetchData,这是个异步的action,会发送网络请求,然后更新state。根据上面我们model中的定义,会在请求结束后拿到数据后更新changelog model中的data数据。同时View也会相应的进行响应。

render函数中是对于界面的描述。

4.2 Ant Design

色老师给我推荐了一个组件库 Ant Design。 我去官网找了个时间轴组件,按照组件介绍的代码直接使用,发现其使用方法非常简单。

使用data数组的 map 方法来返回 Timeline的item。 这一点参考了之前的React实践视频

5、感悟

在别人项目的基础上写代码真是一件过于easy的事情(不是。

正经:在别人项目的基础上进行实践来学习新知识真是一件很爽的事情。我们只需要关注自己需要的部分,想办法解决手头很明确的需求,在此过程中,或者查文档,或者问学长,很快解决之后能够立刻学到很多。在这次Miniproject的过程中,我从对于React一无所知,到尝试使用了 Umi、Dva等等等框架。尽管我对于那些框架其实并没有很了解,但是我相信在之后的实践过程中总有一天会把这些TODO给逐渐消除。

飞雪连天射白鹿 笑书神侠倚碧鸳

这周的私货分享安排给了我,我想把个人读金庸小说的感悟拿出来谈一谈,并且给

引言

海内天涯同飞雪,此去经年谁倚天。

射雕三部成绝唱,人间再无武侠乡。

2018年10月30日,金庸老爷子离开了人世。

逐书分享

  • 飞狐外传、雪山飞狐

    写作顺序: 雪山飞狐 -> 飞狐外传

    故事顺序: 飞狐外传 -> 雪山飞狐

    • 雪山飞狐

      • 主人公

        胡斐、苗人凤、苗若兰、胡一刀

      • 开篇布局

        各色各样的江湖人讲述胡一刀和苗人凤的对决旧事,引出苗人凤和胡斐两个主角。

      • 罗生门式铺陈

        宝树和尚、苗若兰、平阿四等身份职业截然不同的人站在自己的角度来讲述同一个故事。其讲故事的口气均不尽相通。让这个故事显得更加扑朔迷离。第三人称视角讲述的时候,每个人有漏有藏,掺杂自己的心思。让”胡苗对决“更显一丝传奇感。

      • 开放式结尾

        苗人凤此时再无疑心,知道眼前此人必与胡一刀有极深的渊源,叹道:“报应,报应!”闭目待死。胡斐举起树刀,一招就能将他劈下岩去,但想起曾答应过苗若兰,决不能伤她父亲。

        然而若不劈他,容他将一招“提撩剑白鹤舒翅”使全了,自己非死不可,难道为了相饶对方,竟白白送了自己性命么?霎时之间,他心中转过了千百个念头:这人曾害死自己父母,教自己一生孤苦,可是他豪气干云,是个大大的英雄豪杰,又是自己意中人的生父,按理这一刀不该劈将下去;但若不劈,自己决无活命之望,自己甫当壮年,岂肯便死?倘若杀了他吧,回头怎能有脸去见苗若兰?要是终生避开她不再相见,这一生活在世上,心中痛苦,生不如死。

        那时胡斐万分为难,实不知这一刀该当劈是不劈。

        他不愿伤了对方,却又不愿赔上自己性命。

        他若不是侠烈重意之士,这一刀自然劈了下去,更无踌躇。

        但一个人再慷慨豪迈,却也不能轻易把自己性命送了。

        苗若兰站在雪地之中,良久良久,不见二人归来,当下缓缓打开胡斐交给她的包裹。
        只见包裹是几件婴儿衣衫,一双婴儿鞋子,还有一块黄布包袱,月光下看得明白,包上绣著“打遍天下无敌手”七个黑字,正是她父亲当年给胡斐裹在身上的。

        她站在雪地之中,月光之下,望著那婴儿的小衣小鞋,心中柔情万种,不禁痴了。胡斐到底能不能平安归来和她相会,他这一刀到底劈下去还是不劈?

        《雪山飞狐》 金庸

      • 胡斐和苗若兰

        胡斐砰砰心跳,问道:“现在相逢还不迟么?”苗若兰不答,过了良久,轻轻说道:“不迟。”

        又过片刻,说道:“我很欢喜”。

        古人男女风怀恋慕,只凭一言片语,便转倾心之意。

        胡斐听了此言,心中狂喜,说道:“胡斐终生不敢有负。”

        苗若兰道:“我一定学你妈妈,不学我吗。”她这两句话说得天真,可是言语之中,充满了决心,那是把自己一生的命运。全盘交付给了他,不管是好是坏,不管将来是福是祸,总之是与他共同担当。

        两人双手相握,不再说话,似乎这小小山洞就是整个世界,登忘身外天地。

        《雪山飞狐》 金庸

      • 导读与评价

        叙事手法即是优点,也可以是缺点。讲故事的人是市井之人,语言相对平庸。并且各种人物乱入,不认真看容易看不懂。

        雪山飞狐11万字是一部相对较短的小说。

        评价也很高。

    • 飞狐外传

      写在雪山飞狐之后,故事却发生在雪山飞狐之前。写青少年胡斐。

      不免出现矛盾之处。

      • 主人公

        胡斐、程灵素、袁紫衣

      • 袁紫衣

        一张瓜子脸,双眉修长,眼大嘴小,姿形秀丽,容光照人 。

        她声音爽脆清亮,人人均觉动听之至。她虽神色严峻冷傲,面目却甚甜美,令人一见之下,眼光便舍不得离开。

        胡斐俯首听教,好像自己的徒儿一般,不禁大乐,脸上露出笑靥,左颊上酒窝儿微微一凹,心道:“唉!不知这小泥鳅听不听话呢?要是不听话,给人害了,又有谁来救他?”

        火光映照之下,袁紫衣娇脸如花,低语央求,胡斐不由得心肠软了,见到她握着银鞭的手莹白如玉,一股冲动,便想抛下单刀,伸手去握她的小手。

        火光中但见袁紫衣容貌如花,脸生红晕,眼色温柔,全无敌意,目光中似怨似责,又似有些自怨自艾。胡斐不明其意,一怔之下,火花隐灭,殿中黑漆一团。

        思前想后,既自伤身世,又觉不该去撩拨人家,今后不知如何着落,不由得垂下泪来,细听胡斐鼻息渐沉,竟已无心无事地睡去,轻轻地道:“这小泥鲍,他倒睡得着,那也好,他没想我!”直过了半个多时辰,才矇昽睡去。

      • 程灵素

        名字来源《灵枢》《素问》两部医典

        心地善良

        “我师父他老人家谆谆告诫我们,除非万不得已,决计不可轻易伤人。晚辈一生,就从未危害过一条性命。”

        “从今以后,可别太轻易答应人家。世上有许多事情,口中虽然答应了,却是无法办到的呢。”

        “我师父说中了这三种剧毒,无药可治,因为他知道世上没有一个医生,肯不要自己的性命来救活病人。”

        “苗大侠和那毒手药王有仇。江湖上人心难测,倘若他们正是安排恶计,由程姑娘借治伤为名,却下毒手,岂不是我胡斐第二次又给人借作了杀人之刀?这时苗大侠全身穴道放松,只须在要穴中轻轻一针,即能制他死命。”正自踌躇,程灵素回过头来,将小刀交了给他,道:“你给我拿着。”忽见他脸色有异,当即会意,笑道:“苗大侠放心,你却不放心吗?”胡斐道:“倘若是给我治伤,我放一百二十个心。”程灵素道:“你说我是好人呢,还是坏人?”

        这句话单刀直入的问了出来,胡斐绝无思索,随口答道:“你自然是好人。”程灵素很是喜欢,向他一笑。她肌肤黄瘦,本来算不得美丽,但一笑之下,神采焕发,犹如春花初绽。胡斐心中更无半点疑虑,报以一笑。程灵素道:“你真的相信我了吧?”说着脸上微微一红,转过脸去,不敢再和他眼光相对。

        对胡斐的感情

        瞧着她瘦削的侧影,心中大起怜意,说道:“我有一事相求,不知你肯不肯答允,不知我是否高攀得上?”
        程灵素身子一震,颤声道:“你……你说什么?”胡斐从她侧后望去,见她耳根子和半边脸颊全都红了,说道:“你我都无父母亲人,我想和你结拜为兄妹,你说好么?”
        程灵素的脸颊刹时间变为苍白,大声笑道:“好啊,那有什么不好?我有这么一位兄长,当真是求之不得呢?”
        胡斐听她语气中含有讥讽之意,不禁颇为狼狈,道:“我是一片真心。”程灵素道:“我难道是假意?”说着跳下马来,在路旁撮土为香,双膝一屈,便跪在地上。胡斐见她如此爽快,也跪在地上,向天拜了几拜,相对磕头行礼。

        程灵素的悲情

        程灵素冷笑道:“起疑又怎么样?反正你不爱惜自己,便是死在官兵手中,也是活该。”
        胡斐笑道:“我死是活该,只是累得姑娘伤心,那便过意不去。”
        程灵素冷笑道:“你不听我话,自己爱送命,才没人为你伤心呢。除非是你那个多情多义的袁姑娘……她又怎么不来助你一臂之力?”
        胡斐道:“她没知道我会这样傻,竟会闯进福大帅府中去。天下只有一位姑娘,才知道我会这般蛮干胡来,也只有她,才能在紧急关头救我性命。”
        这几句话说得程灵素心中舒服慰贴无比,哼了一声,道:“当年救你性命的是马姑娘,所以你这般念念不忘,要报她大恩。”胡斐道:“在我心中,马姑娘怎能跟我的二妹相比?

      • 程灵素,一片冰心有谁怜

        金庸先生虽吝于把美貌赐于灵素,但他无疑是很钟爱她的。

        张爱玲说:没有一个女人是因为她的灵魂美丽而被爱的。这句话在程灵素身上得到完整体现。

        倪匡先生推其为金大侠小说伤情女子之榜首,极有见地。

        她是一个极品女人,是因为她是自己爱情世界中的极品,是读者心中同情、唏嘘,又不得不肃然起敬的极品。

        程灵素——人生自是有情痴,此恨不关风与月。

        程灵素自然不及黄蓉俏丽,不过是一个“面黄肌瘦”的小丫头,可是她的心思缜密细腻体贴却比黄蓉有过之而无不及。黄蓉的聪明天下皆知颇有几分张扬,而对于程灵素来说,或许除了她的“胡大哥”,谁也值不上她花费更多的心思,正因如此,又更添了几分憨直的可爱?这样的女孩更加难得,却至亲至近,合上书本,恨不得能就此牵了她的手退隐江湖,浪迹天涯!

      • 导读和评价

        这本书风评不算很好。原因有二:1、和《雪山飞狐》剧情冲突。2、胡斐人物形象“渣”,且和《雪山飞狐》相比有很多落差。

      程灵素的人气很高。

      袁紫衣和胡斐的故事也可一读。

      全书37万字,是一部相对平庸的中篇。没有鲜明的想要传达的东西。亮点就是程灵素了吧。

  • 连城诀

    • 主人公

      狄云、血刀老祖、梅念笙

    • 全书书写人性之恶和世道黑暗

      全书除了狄云几乎没有正面人物

      狄云也是浑身缺点

    • 这是一部看完让人对生活失去希望的书

    • 评价与导读

      这本书评价很高。思想十分深刻。十分值得一读

      但建议最后再看。因为它武侠成分少,过于深刻。

  • 天龙八部

    • 主人公

      乔峰、段誉、虚竹

    • 回目成词

      《少年游》

      青衫磊落险峰行。玉壁月华明。马疾香幽。崖高人远。微步縠纹生。

      谁家子弟谁家院。无计悔多情。虎啸龙吟。换巢鸾凤。剑气碧烟横。

      《苏幕遮》

      向来痴。从此醉。水榭听香,指点群豪戏。剧饮千杯男儿事。杏子林中,商略平生义。

      昔时因。今日意。胡汉恩仇,须倾英雄泪。虽万千人吾往矣!悄立雁门,绝壁无馀字。

      《破阵子》

      千里茫茫若梦。双眸粲粲如星。塞外牛羊空许约。烛畔鬓云有旧盟。莽苍踏雪行。

      赤手屠熊搏虎。金戈汤寇鏖兵。草木残生颅铸铁。虫豸[ zhì ]凝寒掌作冰。挥洒缚群英。

      《洞仙歌》

      输赢成败,又争由人算。且自逍遥没谁管。奈天昏地暗,斗转星移。风骤急,缥缈峰头云乱。

      红颜弹指老,刹那芳华。梦里真真语真幻。同一笑,到头万事俱空。胡涂醉,情长计短。解不了,名系贪嗔。却试问,几时把痴心断?

      《水龙吟》

      燕云十八飞骑,奔腾如虎风烟举。老魔小丑,岂堪一击,胜之不武。王霸雄图,血海深恨,尽归尘土。念枉求美眷,良缘安在?枯井底,污泥处。

      酒罢问君三语,为谁开,茶花满路?王孙落魄,怎生消得,杨枝玉露?敝屣荣华,浮云生死,此身何惧!教单于折箭,六军辟易,奋英雄怒!

    • 全书弘扬佛法。全书写人之悲剧

      全书无一人不悲痛缠身

    • 婉妹

      水木清华,婉兮清扬。

    • 评价与导读

      101万,最长的书之一

      必看必看必看

      是金老作品集大成者。布篇恢弘。文学水平很高。

  • 射雕英雄传

    • 主人公

      郭靖、黄蓉、五绝。

    • 射雕三部曲第一部。

    • 地位不用多说。是金庸成名之作,很多人都因射雕入坑。

    • 黄药师

      这本来面目一露,但见他 形相清癯,身材高瘦,风姿隽爽,萧疏轩举,湛然若神。

      他不落俗套,不为世俗思想所规限,他任性痴情而极度浪漫。妻子亡故,他恋恋不忘,十几年中,夜夜在她墓旁吹箫相伴,墓中供着的是他亲笔所绘的小像及最精巧的珍玩,他做了花船,思算携了她的玉棺,月夜出航,让海浪打碎船身,与她一同葬身大海,这是何等痴情,又何等浪漫。而且他的浪漫,不是做梦的少年的浪漫,而是一个懂得爱也懂得欲的成熟男子的浪漫。他的重视真情,一生渴望思念也在所不计。所以,他看见黄蓉深爱郭靖,难舍难分,他便共鸣而发出悲吟:“且乎天地为炉兮,造化为工!阴阳为炭兮,万物为铜!”这样的人自是骄傲的,所以是“东邪”,而黄药师的缺点也太多太多,“伟大”、“英雄”、“完美”的这些字眼不能用到他身上,但这么多才多艺多令人倾心之处的人,有缺点又有什么关系?

    • 导读和评价

      77万字的长篇,但是剧情语言都是十分杰出卓越的。算是一部十分纯粹的武侠。弘扬“侠”的豪气。侠之大者,郭靖生而为人、为民、为国。

      从射雕英雄传入坑,没有任何问题。

  • 白马啸西风

    • 主人公

      李文秀

    • 凄婉的爱情故事

      白马带着她一步一步地回到中原,白马已经老了,只能慢慢地走,但是终是能回到中原的,江南有杨柳,桃花,燕子,金鱼……汉人中有的是英俊勇武的少年,倜傥潇洒的少年……但这个美丽的姑娘,就像高昌国人那样固执,那都是很好很好的,我却偏偏不喜欢。

    • 导读和评价

      全书5万字,是这十四本中最短的短篇。

      讲了一个十分凄婉动人的爱情故事。值得一看。

  • 鹿鼎记

    • 韦小宝和他的七个老婆

    • 起点爽文?

    • 难听点,是厚黑学。好听点,是人际关系的百科全书。这是一部最真实的武侠小说

      韦小宝的智慧在于的平衡。平衡好自己的各种身份,甚至利用各种身份,互惠互利。他监人说人话,见鬼说鬼话,也不失为一种人生艺术。

    • 小玄子和小桂子的友谊

      小桂子战战兢兢跪在厅内,小玄子向门外走去,心想着“这小子对我还算忠心”。

      读到这里,鼻子不禁一酸。

    • 七个老婆

      自己去看,然后挑自己喜欢的吧(不是)

    • 是金老爷子封笔之作。寓意很深。其文笔立意水平之高,更是不言而喻。

    • 回目艺术

      全部是从其先祖查慎行的《敬业堂诗集》中摘录的联句

      纵横勾党清流祸 峭茜风期月旦评

      符来袖里围方解 椎脱囊中事竟成

      。。。

    • 导读和评价

      还是有很多人不喜欢《鹿鼎记》的,我通常认为这种人是比较肤浅的读者,只想看打打杀杀爱恨情仇,而懒得关注人情世故。

      《鹿鼎记》评价很高。确实是一部很好的书。

      全书101万字。是金老爷子最长的两本长篇之一。建议在最后看。看这本书,你可以去品韦小宝的一生,可能对于自己的人生也是一种启迪。

  • 笑傲江湖

    • 主人公

      令狐冲、任盈盈

    • 我不能剧透

    • 这本书伏笔遍布全书,前后四处照应,剧情反转跌宕。

    • 政治隐喻。

    • 真小人是要比 伪君子更让人喜爱的

    • 几个小看点

      • 令狐冲和岳灵珊的虐恋。第一次看这本书的时候,大师兄在思过崖上那段,我的心也随之剧痛。
    • 冲哥和盈盈的自由自在的爱情故事。(我很喜欢盈盈这个角色)

      • 仪琳小师妹的心理描写
    • 莫大先生。虽然笔墨很少,但是我很喜欢的一个人物 (和黄药师很像)

    • 全书到处可见的政治隐喻

    • 导读和评价

      这本书风评极高,很少有差评。

      论武侠成份,无论是正派邪派之争,还是独孤九剑、辟邪剑法、葵花宝典等耳熟能详的武功招式,都是一部令人看后热血沸腾的武侠小说。

      论全书布局,遍布全书的伏笔,相隔大半本书的照应,令人匪夷所思的反转。你一定会为全书情节的精妙拍案叫绝。

      论思想深刻。全书讽刺意味很强。是为数不多没有明确背景时间的书。因为老爷子认为,这样的政治问题会发生在各种地方。

      全书82万,篇幅较长,但真的是一本趣味性很强的书。是那种很替别的那种,从头到尾,趣味性都很强的那种书。

  • 书剑恩仇录

    • 主人公

      陈家洛、红花会众兄弟

    • 开山之作

    • 一个帮会的兄弟反清复明。颇有种古惑仔的那种豪情。我当时看的时候感觉还挺爽。是金庸为数不多的打架永远是打群架的书。

    • 毕竟是第一部。人物形象略显干瘪。立意也很浅。

    • 评价和导读

      上面几条基本就是评价了。这个时候老爷子功力确实还不深。这个书有点像相对优秀的网络小说。(其实本来就是上世纪的网络小说)

      全书43万,不长不短。至于啥时候看,有人认为应该先看,不然看了好的就看不下去这个了。我觉得还是放后面,先看这个没办法入坑。入坑之后再考古多好啊。

  • 神雕侠侣

    • 主人公

      杨过、小龙女、郭襄(我偏要把襄儿写上!)

    • 钟南山后,活死人墓,神雕侠侣,绝迹江湖。

      情 > 武

    • 虐恋

      问世间,情是何物,直教生死相许?天南地北双飞客,老翅几回寒暑。欢乐趣,离别苦,就中更有痴儿女。君应有语,渺万里层云,千山暮雪,只影向谁去?
      横汾路,寂寞当年箫鼓,荒烟依旧平楚。招魂楚些何嗟及,山鬼暗啼风雨。天也妒,未信与,莺儿燕子俱黄土。千秋万古,为留待骚人,狂歌痛饮,来访雁丘处。

      书从李莫愁和武三通的虐恋写起。

      过程中杨过与众女子结识

      郭襄回头过来,见张君宝头上伤口中兀自汨汨流血,于是从怀中取出手帕,替他包扎。张君宝好生感激,欲待出言道谢,却见郭襄眼中泪光莹莹,心下大是奇怪,不知她为甚么伤心,道谢的言辞竟此便说不出口。

      张三丰从身边摸出一对铁铸的罗汉来。
      “这对铁罗汉是百年前郭襄郭女侠赠送于我。你日后送还少林传人。就盼从这对铁罗汉身上,留传少林派的一项绝艺!”

      全书以张三丰和郭襄虐恋结束

    • 杨过和小龙女

      杨过待她走远,笑问:“倘若你是她,便嫁哪一个?”小龙女侧头想了一阵,道:“嫁你。”杨过笑道:“我不算。郭姑娘半点也不欢喜我。我说倘若你是她,二武兄弟之中你嫁哪一个?”小龙女“嗯”了一声,心中拿二武来相互比较,终于又道:“我还是嫁你。”杨过又是好笑,又是感激,伸臂将她搂在怀里。

      十六年后,在此相会,夫妻情深,勿失信约。

      不多剧透

    • 一见杨过误终身

      陆无双、程英、公孙绿萼、完颜萍甚至郭芙

    • 襄儿

      当然一见杨过误终身还有襄儿

      却听得杨过朗声说道:“今番良晤,豪兴不浅,他日江湖相逢,再当杯酒言欢。咱们就此别过。”说着袍袖一拂,携着小龙女之手,与神雕并肩下山。

      其时明月在天,清风吹叶,树巅乌鸦呀啊而鸣,郭襄再也忍耐不住,泪珠夺眶而出。

      正是:

      “秋风清,秋风明;落叶聚还散,寒鸦栖复惊。相思相见知何日,此时此夜难为情。”

      这一来郭襄只吓得魂飞魄丧,当时也不知是为了相救杨过,又或许是情深一往,甘心相从于地下,双足一登,跟着也跃入了深谷……

      可惜我迟生了二十年。倘若妈妈先生我,再生姊姊,我学会了师父的龙象般若功和无上瑜珈密乘,在全真教道观外住了下来,自称大龙女,小杨过在全真教中受师父欺侮,逃到我家里,我收留了他教他武功,他慢慢的自会跟我好了。他再遇到小龙女,最多不过拉住她手,给她三枚金针,说道:小妹子,你很可爱,我心里也挺喜欢你。不过我的心已属大龙女了。请你莫怪!你有什么事,拿一枚金针来,我一定给你办到。

    • 郭芙

      承包了所有反面角色。让人咬牙切齿。

    • 导读与评价

      全书94万字,是比较长的长篇,人物关系和《射雕英雄传》联系较紧密。建议在看完射雕英雄传之后再看。

      整本书的感情线很虐很虐。襄儿、无双、程英终身未嫁。小龙女和杨过深爱彼此也是一段虐恋。

      风评极高。

  • 侠客行

    • 主人公

      石破天

    • 佛之大者

      石破天,始终善良、谦虚、单纯、是个傻小子。

      佛祖降世,仁爱天下,渡众生苦厄。

    • 导读和评价

      36万字,不算长

      整本书言之有物,弘扬佛法。平平淡淡,传颂度不高。

      建议后期再看。

  • 倚天屠龙记

    • 主人公

      张无忌、赵敏

    • 射雕三部曲最后一部

    • 四十回回目押韵 ang

    • 光明左右使 杨逍范遥

    • 导读与评价

      95万 射雕三部曲都是精品

      和前两部关联没有前两部那般紧凑

  • 碧血剑

    • 主人公

      袁崇焕、夏雪宜、袁承志

    • 那个金蛇郎君

      从南来了一群雁,也有成双也有孤单。

      成双的欢天喜地声嘹亮,孤单的落在后头飞不上。

      不看成双,只看孤单。

      细思量你的凄凉,和我是一般样。

      细思量你的凄凉,和我是一般样。

      郎君和温仪的爱情故事

    • 我死磕 袁承志和阿九

    • 导读和评价

      明清三部曲 《书剑恩仇录》《碧血剑》《鹿鼎记》

      可以按顺序看一看

  • 鸳鸯刀

    3万

    仁者无敌

  • 越女剑

    1.6万 一个爱情故事

区块链&&GPU(INA-19-11-25)

这次会大名鼎鼎的任总来做分享。先是一个我没记住名字的学长分享了区块链的知识。
接着是任总对自己目前的研究做了分享。

emmm,这周的内容重在参与hhh,这些东西对于我来说就是扫盲

为什么比特币这么贵

区块链

  • 多方参与的
  • 可信任的
  • 加密
  • 分布式记账本

《Bitcon: a peer-to-peer electronic cash system》

自动唱歌简介

免费GPU

AI音乐+AI唱歌

  • 卡罗尔与星期二 动漫
  • Logic Pro X 作曲软件

任总nb!