#85 Python 解释器

2021-06-21

除了 CPython 之外的几种比较知名的 Python 实现:

  1. PyPy
  2. Jython 原名 JPython,原作者停止维护之后,社区将项目改名并继续维护。
  3. IronPython 运行在 .NET 平台
    之前由微软开发(姊妹项目 IronRuby),后来放弃。项目由志愿者维护。
  4. Pyston Dropbox 停止资助该项目,不过还活着,在继续发版。
  5. Unladen Swallow 谷歌的项目,看介绍,思路和 Pyston 差不多(在 pyston 前面),早死翘翘了
  6. Stackless Python: greenlet 就是从这个项目中弄出来的一个成果。
  7. MicroPython: 专门运行在单片机上(有一个 fork 叫 CircuitPython,似乎也有点名气)

#84 常见的 Python 性能提升手段

2021-06-21

Python 本身的性能现在还很有待提升,但是为什么没有阻碍它的推广呢?

因为 Python 常常被当作是一种胶水语言,它有很好的与 C 互操作性,一直维护与 C 库对接的 API,也就是说很容易可以通过 C 拓展来提升性能。

新的运行时

  • PyPy 据说能有很大提升
  • Pyston LLVM 编译器架构 + JIT
    https://github.com/pyston/pyston
    注意:Dropbox 放弃了这个项目,转向 Go 语言,但这个项目还在继续开发中, 比较活跃
  • Psyco http://psyco.sourceforge.net/ 已经没有维护了,据说只维护到 Python 2.4
    官方推荐 PyPy

局部优化

C/C++ 绑定

  • SWIG: C/C++ 写的代码自动绑定到 Python,就是说生成一个 Python 可以调用的 .so 模块
  • pybind11
  • Boost.Python

直接调用 C

直接开发 CPython 拓展模块(include <Python.h>)也可以,但我不觉得这是一个好办法。

GPU

  • GPULib
  • PyStream
  • PyCUDA
  • PyOpenCL

其他

  • F2PY: Fortran to Python, 将 Fortran 代码自动绑定到 Python, NumPy 的一部分
    也是编译成 .so 包
    我不清楚相关应用场景,以及这个方案是否有性能上的优势

参考资料与拓展阅读

#81 Python 源码学习 02: PyObject

2021-06-19

源码

Include/object.h

/* Nothing is actually declared to be a PyObject, but every pointer to
 * a Python object can be cast to a PyObject*.  This is inheritance built
 * by hand.  Similarly every pointer to a variable-size Python object can,
 * in addition, be cast to PyVarObject*.
 */
typedef struct _object {
    _PyObject_HEAD_EXTRA   // 如果开启了 Py_TRACE_REFS 增加一个 _ob_next, _ob_prev
                           // 使 all live heap objects 组成一个双向链表
    Py_ssize_t ob_refcnt;  // 长整型
    PyTypeObject *ob_type;
} PyObject;

/* Cast argument to PyObject* type. */
#define _PyObject_CAST(op) ((PyObject*)(op))
#define _PyObject_CAST_CONST(op) ((const PyObject*)(op))

typedef struct {
    PyObject ob_base;
    Py_ssize_t ob_size; /* Number of items in variable part */
} PyVarObject;

/* Cast argument to PyVarObject* type. */
#define _PyVarObject_CAST(op) ((PyVarObject*)(op))

#define Py_REFCNT(ob)           (_PyObject_CAST(ob)->ob_refcnt)
#define Py_TYPE(ob)             (_PyObject_CAST(ob)->ob_type)
#define Py_SIZE(ob)             (_PyVarObject_CAST(ob)->ob_size)

PyObject

相当于所有 Python 对象的父类,包含类型,引用计数等信息。

注释说的很清楚,不会有直接声明的 PyObject 变量,只会有 PyObject* 指针,所有指向 Python 对象的指针都可以转换成 PyObject*

PyVarObject

表示 ob_size 个 PyObject,也就是说 PyVarObject 是一个 PyObject 的容器。

#80 NumPy 基础

2021-06-18

NumPy 提供了一个高效的数据结构(数组/矩阵)及对应运算支持,据说效率和 C 接近,是 Python 科学计算生态的基础。

#78 采用霍夫变换矫正倾斜图片

2021-06-04

原图(内容成 15° 娇):

原图

原理

里面设计很多知识点,我一个都不知道,涉及图像处理,数学计算,信号变换等多个方面。

但我查到的资料似乎和下面两个概念相关。

如果看到这篇文章的人了解其中原理能够跟我讲讲 (ninedoors#126),万分感激!

傅立叶变换, Fourier Transform, FT

傅里叶变换是一种线性积分变换,用于信号在时域(或空域)和频域之间的变换,在物理学和工程学中有许多应用。

我的理解是用多个三角函数来表示一个变化曲线。

霍夫变换, Hough Transform

霍夫变换是一种特征提取,被广泛应用在图像分析、电脑视觉以及数位影像处理。霍夫变换是用来辨别找出物件中的特征,例如:线条。他的算法流程大致如下,给定一个物件、要辨别的形状的种类,算法会在参数空间中执行投票来决定物体的形状,而这是由累加空间(accumulator space)里的局部最大值来决定。

维基百科说的很清楚了,用来分析图片特征,识别出其中的形状。

步骤

  1. 原图(灰度处理 + 尺寸裁剪),减少无关数据,减少计算量
  2. 傅里叶变换,得到频域图像
  3. 利用霍夫变换做直线检测
  4. 计算倾斜角度
  5. 旋转校正

代码

import logging
import math

import cv2
import numpy as np
from numpy.lib.function_base import average

logging.basicConfig(level=logging.DEBUG, format='%(asctime)s %(levelname)s [%(name)s:%(funcName)s#%(lineno)s] %(message)s')
LOG = logging.getLogger(__name__)

# 1、灰度化读取文件
filepath = '/tmp/rotated.png'
img = cv2.imread(filepath, 0)
if img is None:
    LOG.error('image not exists!')
    exit()

# 2、图像延扩
h, w = img.shape[:2]
new_h = cv2.getOptimalDFTSize(h)  # 傅里叶最优尺寸
new_w = cv2.getOptimalDFTSize(w)
right = new_w - w
bottom = new_h - h
# 边界扩充 cv2.copyMakeBorder(src, top, bottom, left, right, borderType, dst=None)
#   BORDER_CONSTANT    常量,增加的变量通通为 value
#   BORDER_REFLICATE   用边界的颜色填充
#   BORDER_REFLECT     镜像
#   BORDER_REFLECT_101 倒映
#   BORDER_WRAP        没有规律
nimg = cv2.copyMakeBorder(img, 0, bottom, 0, right, borderType=cv2.BORDER_CONSTANT, value=0)
cv2.imshow('new image', nimg)

# 3、傅里叶变换,获到频域图像
f = np.fft.fft2(nimg)
fshift = np.fft.fftshift(f)
magnitude = np.log(np.abs(fshift))
LOG.info(magnitude)
# 二值化
magnitude_uint = magnitude.astype(np.uint8)
ret, thresh = cv2.threshold(magnitude_uint, 11, 255, cv2.THRESH_BINARY)
LOG.info(ret)
cv2.imshow('thresh', thresh)
LOG.info(thresh.dtype)

# 4、霍夫直线变换
lines = cv2.HoughLinesP(thresh, 2, np.pi/180, 30, minLineLength=40, maxLineGap=100)
LOG.info('line number: %d', len(lines))
# 创建一个新图像,标注直线
lineimg = np.ones(nimg.shape, dtype=np.uint8)
lineimg = lineimg * 255
for index, line in enumerate(lines):
    LOG.info('draw line#%d: %s', index, line)
    x1, y1, x2, y2 = line[0]
    cv2.line(lineimg, (x1, y1), (x2, y2), (0, 255, 0), 2)
cv2.imshow('line image', lineimg)

# 5、计算倾斜角度
piThresh = np.pi / 180
pi2 = np.pi / 2
angles = []
for line in lines:
    LOG.info('line#%d: %s <===============', index, line)
    x1, y1, x2, y2 = line[0]
    if x2 - x1 == 0:
        LOG.debug('skip 1')
        continue
    theta = (y2 - y1) / (x2 - x1)
    LOG.debug('theta: %r', theta)
    if abs(theta) < piThresh or abs(theta - pi2) < piThresh:
        LOG.debug('skip 2: %r', theta)
        continue
    angle = math.atan(theta)
    LOG.info('angle 1: %r', angle)
    angle = angle * (180 / np.pi)
    LOG.info('angle 2: %r', angle)
    angle = (angle - 90)/(w/h)
    LOG.info('angle 3: %r', angle)
    angles.append(angle)

if not angles:
    LOG.info('图片挺正的了,别折腾!')
else:
    # from numpy.lib.function_base import average
    # angle = average(angles)
    LOG.info('  方差: %r', np.array(angles).var())
    LOG.info('标准差: %r', np.array(angles).std())
    # angle = np.mean(angles)
    import statistics
    # LOG.debug(statistics.multimode(angles))
    # angle = statistics.mode(angles)
    # statistics.StatisticsError: geometric mean requires a non-empty dataset  containing positive numbers
    # statistics.StatisticsError: harmonic mean does not support negative values
    angle = statistics.median(angles)
    if 180 > angle > 90:
        angle = 180 - angle
    elif -180 < angle < -90:
        angle = 180 + angle
    LOG.info('==> %r, %r', angles, angle)

    # 6、旋转
    center = (w//2, h//2)
    M = cv2.getRotationMatrix2D(center, angle, 1.0)
    LOG.debug('=========== RotationMatrix2D ===========')
    for i in M:
        LOG.debug(i)
    LOG.debug('^======================================^')
    rotated = cv2.warpAffine(img, M, (w, h), flags=cv2.INTER_CUBIC, borderMode=cv2.BORDER_REPLICATE)
    cv2.imshow('rotated', rotated)

cv2.waitKey(0)
cv2.destroyAllWindows()

参考资料与拓展阅读