张春成

V2

2022/09/10阅读:18主题:默认主题

鸡同鸭讲的 BUG

鸡同鸭讲的 BUG

Python-opencv 遇到了一个中文支持的 BUG,它不能读取目录中带有中文的图像文件。

这背后的原因是编码和解码过程不一致所导致的乱码现象。


现象描述

简单来说,就是当文件的路径包含中文的时候,cv2 的 imread 函数不能正常读取图像的 RGB (BGR) 矩阵,下面是测试程序。

# %%
import os
import cv2
from pathlib import Path

# %%
# Make file names, eng for name in english chars, chs for name in chinese chars
# And make sure they exist
engName = Path(os.environ['HOME'], 'Pictures''BingImageOfTheDay.jpg')
engName, engName.is_file()

chsName = Path(os.environ['HOME'], 'Pictures''图片.jpg')
chsName, chsName.is_file()

# %%
# The imread works fine, it reads BGR color images
mat1 = cv2.imread(engName.as_posix())
mat1.shape

# %%
# The imread fails
mat2 = cv2.imread(chsName.as_posix())
type(mat2)

# %%

问题追踪

经过一系列查询,可以逐步将可能出问题的原码定位到 Opencv 的原码中,位置为:modules/imgcodecs/src/loadsave.cpp#L420L421 和 modules/imgcodecs/src/grfmt_jpeg.cpp#L240L242 和 modules/imgcodecs/src/grfmt_base.cpp#L65L67

opencv/loadsave.cpp at b0dc474160e389b9c9045da5db49d03ae17c6a6b · opencv/opencv

opencv/grfmt_jpeg.cpp at b0dc474160e389b9c9045da5db49d03ae17c6a6b · opencv/opencv

opencv/grfmt_base.cpp at b0dc474160e389b9c9045da5db49d03ae17c6a6b · opencv/opencv

Untitled
Untitled
Untitled
Untitled
Untitled
Untitled

可以看到,对于这个玩意,opencv 的 c++ 原码并没有对文件名做任何“操作”。但这并没有达到无为无不为的境界,而是把一个问题埋在了系统里,这个问题就是文字编码。

/// file: loadsave.cpp
/// set the filename in the driver
decoder->setSource( filename );

/// file: grfmt_base.cpp
/// set the m_filename
bool BaseImageDecoder::setSourceconst String& filename )
{
    m_filename = filename;
    m_buf.release();
    return true;
}

/// file: grfmt_jpeg
/// open the m_filename, take jpeg for example
m_f = fopen( m_filename.c_str(), "rb" );
if( m_f )
    jpeg_stdio_src( &state->cinfo, m_f );

简单来说,就是 python 3 对字符串的处理是统一使用 Unicode 编码,这本来是好事,但这个好事只停留在 python 内部。这是因为它运行在系统提供的环境下,而 windows 系统对于中文区系统的文字编码方法有点微妙,因为在没有选择 Beta 版的 Unicode UTF-8 支持的情况下,系统会以 GBK 对中文字符进行编码。

Untitled
Untitled

这就导致一个问题,那就是 python 在存储字符串的时候使用的编码是 utf-8,而系统的其他成员在默认情况下,使用 gbk 对它进行解码,这一定会导致冲突,样例如下。

import sys

# Output: utf-8 or gbk, depends on OS setting
sys.getdefaultencoding()

# Encoding conflict, 中秋节: 涓绉嬭妭
s = '中秋节'
decode = s.encode('utf-8').decode('gbk', errors='ignore')
print('{}: {}'.format(s, decode))

问题延展

凡是涉及到通信的系统,都需要对编码和解码端进行约定和匹配。因为这个现象并不限于中文字符的编码。下面是一个针对数字的编解码测试样例,它涉及以下的编、解码排列组合

  • 编码方案:4 个字表示一个数,这个数可以是整数(int)或浮点数(float);
  • 编码方案:比特位的排列方向为 ”大端(big)“ 和 ”小端(little)“;
  • 解码方案:解码方法有四种,分别是 '>f', '<f', '>i', '<i', 其中 ‘>’ 代表大端,’<’ 代表小端,‘i‘代表整数,’f’代表浮点数。

实验的对比结果如下

| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |

import struct
import numpy as np
import pandas as pd

# -------------------------------------
# Material setting
ints = [65536 + e for e in range(2)]
floats = [3.14 + e for e in range(2)]

orders = ['little''big']

# -------------------------------------
# Encode

data = []
columns = []

# Encode int
for order in ['little''big']:
    for num in ints:
        d = num.to_bytes(4, order)
        data.append(d)
        columns.append('{}-{}'.format(order, num))

# Encode float
for order in ['>f''<f']:
    for num in floats:
        d = struct.pack(order, num)
        data.append(d)
        columns.append('{}-{:.2f}'.format(order, num))

d = b''.join(data)

# -------------------------------------
# Decode

dtypes = ['>f''<f''>i''<i']

data=[]

for dt in dtypes:
    decode = np.frombuffer(d, dtype=dt)
    data.append([dt, decode.dtype] + decode.tolist())
    print('{}: {}, {}'.format(dt, decode, decode.dtype))

df = pd.DataFrame(data, columns=['dt1''dt2']+columns)
df.to_csv('data.csv')
df

分类:

后端

标签:

后端

作者介绍

张春成
V2