前言:
有函数式就是简单的定积分问题,在没有具体的数据和函数式的请跨下,我们可以考虑计算封闭图形的像素面积,解决下图问题。
大家好,这里是滑稽研究所。开门见山,本期我们需要解决的问题如下。
对于任意给出的黑色曲线,自动计算出A值,使A对应的红色虚线与坐标轴构成的区域面积=黑色曲线与坐标轴构成的区域面积。
其实就是面积问题,如果曲线有式子的话就是一个定积分的问题。那么很显然图中并没有给出式子或者相关数据点。说简单一点就是求面积的问题。先说一下我的解题思路。
1.建造一个标准的直角坐标系,在坐标系范围任意的绘制曲线,如下。
我们绘制了三条曲线以验证我们程序的泛用性。
2.对坐标轴和曲线进行处理,识别曲线起点和终点,在终点处向x轴做垂直线,然后使用opencv消去多余的坐标轴,使曲线和直角坐标系形成一个封闭的图形,如下。
3.经过可以看到代码自动消除多余坐标轴时,可能会使我们的图形产生缺口,这样不利于我们识别轮廓并计算面积,因此我们需要对图像做一次膨胀处理消除缺口。
可以看到经过膨胀处理后,缺口消失了。同时我们还进行了灰度处理和轮廓处理。这时我们就已经得到了我们想要的封闭轮廓。对精确度要求较高的朋友在膨胀处理之后可以再做一次侵蚀处理,这样面积计算更精确。
4.经过上述步骤之后,我们已经得到了曲线的起点、终点坐标以及封闭图形的面积,我们用面积除曲线终点的横坐标(即矩形的一边)可得出A值(即矩形的另一边)。并且我们知道,得到的矩形和曲线围成的封闭图形面积是相等的。下面是运行结果。
这里没有我们只绘制了A的值,没有绘制矩形的另一边。到这里我们就完成了对曲线的处理。
我采用了1000*600的画框,大家可以根据实际的坐标轴进行等比缩放。除此之外,如果你想识别纸上的曲线,对图形的处理是一样的思路。我们只需将纸上的直角坐标系拍摄成图片然后进行上述步骤处理即可。甚至识别纸上的曲线要更简单,因为我们可以人工的处理曲线和直角坐标系。比如手动擦除多余的坐标轴,直接使用程序计算人工处理好的封闭图形面积即可。但我们面对很多张显示的图像时,手工就显得有点吃力了。
下面我们上代码,代码分为两个部分。
源代码1:
建造标准直角坐标系,并完成绘图功能。也就是我制作了一个简单的画图工具,可以用鼠标直接绘制曲线。得到我们第一步看到的那些原始曲线图。此外我们还做了橡皮擦功能和坐标轴误擦自动修复功能。
import cv2
import time
import numpy as np
# 当鼠标按下时变为 True
drawing = False
# 如果 mode 为 true 绘制矩形。按下'm' 变成绘制曲线。
mode = True
ix, iy = -1, -1
# 创建回调函数
def draw_circle(event, x, y, flags, param):
global ix, iy, drawing, mode
# 当按下左键是返回起始位置坐标
y = abs(y-600)
print('横坐标:',x,'纵坐标',y )
if event == cv2.EVENT_LBUTTONDOWN :
drawing = True
ix, iy = x, y
# 当鼠标左键按下并移动是绘制图形。event 可以查看移动, flag 查看是否按下
elif event == cv2.EVENT_MOUSEMOVE and flags == cv2.EVENT_FLAG_LBUTTON:
if drawing == True and x >= 50 and y >= 50:
if mode == True:
cv2.circle(img, (x, abs(y-600)), 1, (0, 0, 0), -1)
else:
# 绘制圆圈,小圆点连在一起就成了线, 3 代表了笔画的粗细
cv2.circle(img, (x, abs(y-600)), 10, (255, 255, 255), -1)
# 下面注释掉的代码是起始点为圆心,起点到终点为半径的
elif event == cv2.EVENT_LBUTTONUP:
drawing == False
img = np.zeros((600,1000,3), np.uint8)
# 使用白色填充图片区域,默认为黑色
img.fill(255)
cv2.line(img, (50, 0), (50, 550), (0, 0, 0), 2)
cv2.line(img, (50, 550), (1000, 550), (0, 0, 0), 2)
cv2.namedWindow('image')
cv2.setMouseCallback('image', draw_circle)
filename = 'pic_1.jpg'
progress = True
while progress:
cv2.imshow('image', img)
k = cv2.waitKey(1) & 0xFF
if k == ord('m'):
mode = not mode
elif k == ord('d'):
cv2.line(img, (50, 0), (50, 550), (0, 0, 0), 2)
cv2.line(img, (50, 550), (1000, 550), (0, 0, 0), 2)
cv2.imwrite(filename, img)
elif k == ord('q'):
progress = False
代码2:
对原始曲线图进行上述步骤处理。
import cv2
import numpy as np
img = cv2.imread("pic_1.jpg")
kernel = np.ones((5,5),np.uint8)
imgGray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
imgBlur = cv2.GaussianBlur(imgGray,(7,7),1)
#获取图形轮廓
imgCanny = cv2.Canny(imgBlur,50,50)
cv2.imshow("qxlk", imgCanny)
img1 = img.copy()
xx = imgGray.shape[0]
l1 = []
l2 = []
def getContours(img):
contours,hierarchy = cv2.findContours(img,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_NONE)
#contours接受识别出的所有轮廓
#hierarchy各个轮廓之间的关系,我们本次用不到。
for cnt in contours:
area = cv2.contourArea(cnt)
#这个输出各个轮廓的面积
if area >= 10:
# 给我们的轮廓描边,蓝色
#cv2.drawContours(img1, cnt, -1, (0, 0, 0), 1)
# 轮廓的长度
peri = cv2.arcLength(cnt, True)
# 找出轮廓的突变值
approx = cv2.approxPolyDP(cnt, 0.02 * peri, True)
# approx找到的是一个轮廓有几个突变值,有几个角就会有几个突变值
# 返回的是一个list,输出他的长度,就可以知道到底有几个角
#输出最大的横坐标就是边界值。
#曲线最右端的横纵坐标
for i in approx:
l1.append(i[0][0])
l2.append(i[0][1])
#对应的最大值的下标
l1max = l1.index(max(l1))
#剔除坐标系最右边的突变值即x轴终点。
l1.pop(l1max)
l2.pop(l1max)
l1max = l1.index(max(l1))
#得到曲线边界点坐标 ,笛卡尔坐标系
print('曲线终点坐标:',max(l1),l2[l1max])
t_point = [max(l1),l2[l1max]]
#从曲线边界向x轴做垂直线。
cv2.line(img1, (max(l1), 550), (max(l1), l2[l1max]), (0, 0, 0), 2)
# 使用白线遮盖曲线终点后方的x轴,方便后面识别面积。
cv2.line(img1, (1000, 550), (max(l1)+3, 550), (255, 255, 255), 3)
#剔除y轴上的最大最小突变坐标,即原点和y轴终点
l2min = l2.index(min(l2))
l2.pop(l2min)
l1.pop(l2min)
l2max = l2.index(max(l2))
l2.pop(l2max)
l1.pop(l2max)
#选出最靠近y轴的突变值点,即曲线起点。
l1min = l1.index(min(l1))
print('曲线起点坐标:',min(l1),l2[l1min])
#使用白线遮盖曲线起点上方的y轴,方便后面识别面积。
cv2.line(img1, (min(l1)-1, 0), (min(l1)-1,l2[l1min]), (255, 255, 255), 3)
return t_point
ter = getContours(imgCanny)[0]
cv2.imshow("G1",img1)
print('img1:',img1.shape)
imgG2 = cv2.cvtColor(img1,cv2.COLOR_BGR2GRAY)
imgB2 = cv2.GaussianBlur(imgG2,(7,7),1)
#获取图形轮廓
imgC2 = cv2.Canny(imgB2,50,50)
#做一次膨胀操作,防止遮盖坐标轴的时候轮廓产生缺口
imgC2 = imgDialation = cv2.dilate(imgC2,kernel,iterations=1)
cv2.imshow("44", imgC2)
img = cv2.imread("pic_1.jpg")
def get2(img):
contours,hierarchy = cv2.findContours(img,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_NONE)
#contours接受识别出的所有轮廓
#hierarchy各个轮廓之间的关系,我们本次用不到。
for cnt in contours:
area = cv2.contourArea(cnt)
print('曲线与直角坐标系围成的面积为:',area)
#这个输出各个轮廓的面积
A_h = area/(ter-50)
print('A的值为:',A_h)
#绘制出
return A_h
get2(imgC2)
print(img.shape)
A_h = get2(imgC2)
#绘制A
cv2.line(img, (50, 600-50-int(A_h)), (ter, 600-50-int(A_h)), (0, 0, 255), 3)
cv2.imwrite('result_001.jpg', img)
cv2.imshow("result", img)
cv2.waitKey(0)
以上就是我们本期的全部内容了。可以看到我并没有合并这两个程序。因为我觉得第一个程序单独作为一个画图小工具也是蛮好用的。程序还有许多可以优化的地方。欢迎小伙伴们在评论区一起讨论。