文章目录
摘要: 将项目实践中比较常用的自定义函数集合汇总, 主要是Python函数, 便于快速算法验证
import matplotlib.pyplot as plt
import cv2 as cv
视频帧及bbox读取
import cv2 as cv
frame_sequence = "base"
fix_mode = True
skip_frames = 592
fps_disp = 25
def onMouse(event, x, y, flags, param):
disp = param["img"].copy()
roi = param["roi"]
if event == cv.EVENT_MOUSEMOVE:
roi[0] = x - roi[2] // 2
roi[1] = y - roi[3] // 2
pt1 = tuple(roi[:2])
pt2 = tuple([int(x + y) for x, y in zip(pt1, roi[2:4])])
cv.rectangle(disp, pt1, pt2, [0, 255, 0], 2)
cv.imshow("frame_sequence", disp)
cv.waitKey(1)
def main():
# 读取视频帧序列
cap = cv.VideoCapture(frame_sequence + "/%4d.jpg")
# 读取 groundtruth
with open(frame_sequence + "/groundtruth_rect.txt", "r") as f:
roi_gt_list = f.readlines()
# 循环显示视频帧
length = len(roi_gt_list)
for i in range(length):
_, frame = cap.read()
if frame is None:
break
if i < skip_frames:
continue
# 转换 roi,原格式 "11.5,22,33.5,44\n"
roi_gt = roi_gt_list[i].strip("\n").split(",")
roi_gt = [round(float(x)) for x in roi_gt]
# 显示视频帧及标注框
disp = frame.copy()
pt1 = tuple(roi_gt[:2])
pt2 = tuple([int(x + y) for x, y in zip(pt1, roi_gt[2:4])])
cv.rectangle(disp, pt1, pt2, [0, 255, 0], 2)
font = cv.FONT_HERSHEY_SIMPLEX
cv.putText(disp, "frame: " + str(i), (20, 60), font, 1, (0, 255, 0), 2, cv.LINE_AA)
cv.imshow("frame_sequence", disp)
cv.waitKey(round(1000 / fps_disp))
# 选择修复
labeled = False
run = True
roi = roi_gt.copy()
while fix_mode:
key = cv.waitKey(0)
if key == 27: # ESC 退出
run = False
break
elif key == 32: # 空格下一帧
break
elif key in [ord('q'), ord('Q')]: # Q键 修复标注
cv.imshow("frame_sequence", frame)
params = {"img": frame, "roi": roi}
cv.setMouseCallback("frame_sequence", onMouse, params)
cv.waitKey(0)
labeled = True
break
else: # 禁止其他键
continue
# 写入标注并显示
if labeled:
roi_str = str(roi[0]) + ',' + \
str(roi[1]) + ',' + \
str(roi[2]) + ',' + \
str(roi[3]) + "\n"
roi_gt_list[i] = roi_str
disp = frame.copy()
pt1 = tuple(roi[:2])
pt2 = tuple([int(x + y) for x, y in zip(pt1, roi[2:4])])
cv.rectangle(disp, pt1, pt2, [0, 255, 0], 2)
cv.imshow("labeled", disp)
cv.waitKey(0)
cv.destroyAllWindows()
# 退出
if not run:
cv.destroyAllWindows()
break
# 写入文件
with open(frame_sequence + "/groundtruth_rect.txt", "w") as f:
f.writelines(roi_gt_list)
cv.waitKey(0)
cv.destroyAllWindows()
if __name__ == '__main__':
main()
plt 单幅图片显示
- 常用在jupyter中
- 自动显示
BGR
彩色图和灰度图. 注意是opencv::imread
读入的BGR彩色图 - 可设置缩放比例. scale=0.5,即横向纵向都缩小为1/2
def imshow(img, scale=None, name=None, axis=False):
assert img is not None, "image is not exit"
if scale:
assert 0 <= scale <= 1, "0 <= scale <= 1 "
height, width = img.shape[:2]
plt.figure(figsize=(width*scale/70, height*scale/70), dpi=70)
else:
plt.figure()
isColor = True if img.ndim == 3 else False
if isColor:
plt.imshow(img[:,:,::-1])
else:
plt.imshow(img, cmap="gray")
ax = "image" if axis else "off"
plt.title(name)
plt.axis(ax)
plt.show()
imshow(src, 0.5)
常见色彩空间的分量显示
def imColorSpace(src):
assert src.ndim == 3, "Input image must be 3 channels"
imgray = cv.cvtColor(src, cv.COLOR_BGR2GRAY)
hsv = cv.cvtColor(src, cv.COLOR_BGR2HSV)
luv = cv.cvtColor(src, cv.COLOR_BGR2LUV)
lab = cv.cvtColor(src, cv.COLOR_BGR2LAB)
plt.figure(figsize=(15, 25), dpi=72)
plt.subplot(5,3,1), plt.imshow(src[:,:,::-1]), plt.axis("off"), plt.title("Source")
plt.subplot(5,3,2), plt.imshow(imgray,cmap="gray"), plt.axis("off"), plt.title("Gray")
plt.subplot(5,3,4), plt.imshow(src[:,:,0],cmap="gray"), plt.axis("off"), plt.title("BGR-B")
plt.subplot(5,3,5), plt.imshow(src[:,:,1],cmap="gray"), plt.axis("off"), plt.title("BGR-G")
plt.subplot(5,3,6), plt.imshow(src[:,:,2],cmap="gray"), plt.axis("off"), plt.title("BGR-R")
plt.subplot(5,3,7), plt.imshow(hsv[:,:,0],cmap="gray"), plt.axis("off"), plt.title("HSV-H")
plt.subplot(5,3,8), plt.imshow(hsv[:,:,1],cmap="gray"), plt.axis("off"), plt.title("HSV-S")
plt.subplot(5,3,9), plt.imshow(hsv[:,:,2],cmap="gray"), plt.axis("off"), plt.title("HSV-V")
plt.subplot(5,3,10), plt.imshow(luv[:,:,0],cmap="gray"), plt.axis("off"), plt.title("LUV-L")
plt.subplot(5,3,11), plt.imshow(luv[:,:,1],cmap="gray"), plt.axis("off"), plt.title("LUV-U")
plt.subplot(5,3,12), plt.imshow(luv[:,:,2],cmap="gray"), plt.axis("off"), plt.title("LUV-V")
plt.subplot(5,3,13), plt.imshow(lab[:,:,0],cmap="gray"), plt.axis("off"), plt.title("LAB-L")
plt.subplot(5,3,14), plt.imshow(lab[:,:,1],cmap="gray"), plt.axis("off"), plt.title("LAB-A")
plt.subplot(5,3,15), plt.imshow(lab[:,:,2],cmap="gray"), plt.axis("off"), plt.title("LAB-B")
plt.tight_layout()
plt.show()
二值图像的联通域显示
def imConnectedComponent(imbin):
assert imbin.ndim == 2, "Input image must be 2 channels"
drawing = np.zeros([imbin.shape[0], imbin.shape[1], 3], dtype="uint8")
ccNum, labels, stats, centroids = cv.connectedComponentsWithStats(imbin)
colors = [np.array([0,0,0])] + [np.random.rand(3)*256 for i in range(ccNum-1)]
for i in range(ccNum):
x1, y1, width, height, count = stats[i]
print("Connected Component %2d: (%4d,%4d), width=%4d, height=%4d, centroids=(%d, %d)"
% (i, x1, y1, width, height, centroids[i,0], centroids[i,1]))
# 涂色, label 0 黑色背景
drawing[labels==i] = colors[i]
plt.figure(figsize=(12,6))
plt.subplot(1,2,1), plt.imshow(imbin,cmap="gray"), plt.axis("off"), plt.title("Source")
plt.subplot(1,2,2), plt.imshow(drawing[:,:,::-1]), plt.axis("off"), plt.title("Connected Component")
plt.show()
显示统计直方图
def imhist(img, sharey=False):
assert img is not None, "image is not exit"
isColor = True if img.ndim == 3 else False
if isColor:
fig, (ax1,ax2,ax3) = plt.subplots(1,3,sharey=sharey,figsize=(12,3))
ax1.hist(img[:,:,0].ravel(), bins=256, range=(0,256), color="b"),ax1.set_xticks(range(0,256,50))
ax2.hist(img[:,:,1].ravel(), bins=256, range=(0,256), color="g"),ax2.set_xticks(range(0,256,50))
ax3.hist(img[:,:,2].ravel(), bins=256, range=(0,256), color="r"),ax3.set_xticks(range(0,256,50))
ax2.set_xlabel("level"), ax1.set_ylabel("frequency")
plt.tight_layout()
else:
plt.figure(figsize=(15,3))
plt.hist(img.ravel(), bins=256, range=(0,256))
plt.title("Statistical Histogram")
plt.xlabel("level"), plt.ylabel("frequency")
plt.xticks(range(0,256,20))
plt.show()
imhist(hsv)
imhist(hsv, False)
imhist(hsv[:, :, 2])
颜色过滤生成二值图
- 适用三通道/单通道图像
def imColorFilter(img, lower, upper):
channels= 3 if img.ndim == 3 else 1
assert len(lower) == len(upper) == channels
lower = np.array(lower)
upper = np.array(upper)
imcolor = cv.inRange(img,lower,upper)
return imcolor
imcolor= imColorFilter(src, [180,150,100], [230,200,130])
Hough 圆检测
void cv::HoughCircles( InputArray image,
OutputArray circles,
int method,
double dp,
double minDist,
double param1 = 100,
double param2 = 100,
int minRadius = 0,
int maxRadius = 0
)
Python:
circles = cv.HoughCircles(image, method, dp, minDist[, circles[, param1[, param2[, minRadius[, maxRadius]]]]])
参数:
- image 8位,单通道灰度化的输入图像
- circles Output vector of found circles.Each vector is encoded as 3 or 4 element floating - point vector(x, y, radius) or (x, y, radius, votes) .
- method Detection method.Currently, the only implemented method is HOUGH_GRADIENT (v4.1.2)
- dp 累加器分辨率与图像分辨率的反比,For example, if dp = 1, the accumulator has the same resolution as the input image.If dp = 2, the accumulator has half as big widthand height.
- minDist Minimum distance between the centers of the detected circles.If the parameter is too small, multiple neighbor circles may be falsely detected in addition to a true one.If it is too large, some circles may be missed.
- param1 the higher threshold of the Canny edge detector(the lower one is twice smaller).
- param2 圆心累加器阈值. 值越小,可能会检测到更多的假圆,优先返回累加器值较大的圆。
- minRadius Minimum circle radius.
- maxRadius Maximum circle radius.If <= 0, uses the maximum image dimension.If < 0, returns centers without finding the radius.
示例代码:
using namespace cv;
int main(int argc, char** argv)
{
Mat img, gray;
img = imread(argv[1], 1);
cvtColor(img, gray, COLOR_BGR2GRAY);
// smooth it, otherwise a lot of false circles may be detected
GaussianBlur(gray, gray, Size(9, 9), 2, 2);
vector<Vec3f> circles;
double dp{ 2 }, minDist{ gray.rows / 4 }, CannyHighThres{ 200 }, accumulatorThres{ 100 };
int minRadius{ 0 }, maxRadius{ 0 };
HoughCircles(gray, circles, cv::HOUGH_GRADIENT, dp, minDist, CannyHighThres, accumulatorThres);
for (size_t i = 0; i < circles.size(); i++)
{
Point center(cvRound(circles[i][0]), cvRound(circles[i][1]));
int radius = cvRound(circles[i][2]);
// draw the circle center
circle(img, center, 3, Scalar(0, 255, 0), -1, 8, 0);
// draw the circle outline
circle(img, center, radius, Scalar(0, 0, 255), 3, 8, 0);
}
namedWindow("circles", 1);
imshow("circles", img);
waitKey(0);
return 0;
}
Hough 线检测
寻找轮廓
def imContour(imbin, drawing=None):
assert imbin.ndim == 2, "Input must be single-channel binary image"
contours, _ = cv.findContours(imbin, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)
print(f'共寻找到 {len(contours)} 条轮廓\n')
if drawing is None:
dst = np.zeros([imbin.shape[0], imbin.shape[1]], dtype="uint8")
else:
dst = drawing.copy()
cv.drawContours(dst, contours, -1, 255, 3)
return dst
dst = imContour(imcolor, src)
dst = imContour(imcolor)
计算直线交点
- 考虑某条直线斜率不存在
- 考虑两直线平行, 交点不存在
- 不考虑交点是否位于线段内
def getCrossPoint(lineA, lineB):
assert len(lineA) == len(lineB) == 4, "len(line) != 4"
k1 = (lineA[3]-lineA[1]) / (lineA[2]-lineA[0]+1e-4)
k2 = (lineB[3]-lineB[1]) / (lineB[2]-lineB[0]+1e-4)
b1 = lineA[1] - k1*lineA[0]
b2 = lineB[1] - k2*lineB[0]
assert abs(k1-k2) > 0.1, f"Parallel lines have no cross point.{k1:.2f},{k2:.2f}"
if k1 > 1e4:
x0 = lineA[0]
y0 = k2*x0+b2
elif k2 > 1e4:
x0 = lineB[0]
y0 = k1*x0+b1
else:
x0 = -(b1-b2)/(k1-k2)
y0 = k1*x0+b1
return int(x0), int(y0)
添加Gaussian噪声
- 参数: 均值 μ, 方差 σ2
- python 广播机制保证了输入可以是彩色图或者灰度图
def addGaussianNoise(img, mean=0, var=0.01):
scale = 255.0 if img.max()>1.0 else 1.0
img = np.array(img/scale, dtype=float)
noise = np.random.normal(mean, var**0.5, img.shape)
dst = img + noise
dst[dst<0] = 0
dst[dst>1] = 1
dst *= 255
return dst.astype("uint8")
添加椒盐噪声
- 参数: 椒盐噪声强度(密度, 即占比); 椒盐比例
- python 广播机制保证了输入可以是彩色图或者灰度图
- 计算噪声强度:
1 - (img==dst).sum()/(img==img).sum()
def addSPNoise(img, density=0.05, proportion=0.5):
assert 0<density<1, "Noise density on range (0,1)"
assert 0<proportion<1, "Proportion of salt noise on range (0,1)"
dst = img.copy()
prob_salt = density*proportion
prob_pepper = density*(1-proportion)
thres = 1-prob_pepper
for i in range(img.shape[0]):
for j in range(img.shape[1]):
rand = np.random.rand()
if rand < prob_salt:
dst[i][j] = 255
elif rand > thres:
dst[i][j] = 0
else:
pass
return dst