前言
OpenCV图像处理 — 1
闲来无事又把现在学到的分享出来。
如果帮助到你我很荣幸,如果你有更好的方法和建议可以私信,乐于接受,愿意交流。
说明
环境:树莓派5 Raspberry Pi OS OpenCV Python CSI500万摄像头 STM32F1
内容:树莓派OpenCV简单项目方案 STM32F1串口通信方法
QR-code二维码 字符提取OCR 网格循迹对齐
OpenCV_Projects
以下应用场景结合 上一篇《OpenCV图像处理 — 1》更佳
Tesseract-OCR引擎
Python提供了支持Tesseract-OCR引擎的Python版本的库pytesseract,pytesseract是一款用于光学字符识别的python工具,即从图片中识别出和读取其中的文字。
pytesseract是该引擎的一种封装,也可以单独作为对Tesseract引擎的调用脚本,支持使用PIL库(Python Image Library)读取各种图片文件类型,包括jpeg,png,gif,bmp等。作为脚本使用时,pytesseract将打印识别出的文字。
树莓派5安装
sudo apt-get update
sudo apt-get install libleptonica-dev
sudo apt-get install tesseract-ocr
sudo apt-get install libtesseract-dev
pip3 install pytesseract
pip3 install pillow
Python只是提供了调用Tesseract的接口,方便我们在Python程序中使用Tesseract,实际运行在Tesseract中。
函数image_to_string
pytesseract.image_to_string(Cropped, config='--psm 11')
参考文章:pytesseract库中的image_to_string函数各参数解释_pytesseract config-CSDN博客
应用实例-车牌识别
环境JupterLab代码块
不同python代码块代表jupyterlab代码块
import cv2
import imutils
import numpy as np
import pytesseract
img = cv2.imread("cd../CAR_License.webp",cv2.IMREAD_COLOR)
路径选择自己的图片路径,放一张可以拿来用的图片
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
gray = cv2.bilateralFilter(gray, 5, 15, 15)
cv2.bilateralFilter
图像滤波(双边滤波)
一些滤波方法会对图像造成模糊,使得边缘信息变弱。
因此需要一种能够对图像边缘信息进行保留的滤波算法,双边滤波是综合考虑空间信息和色彩信息的滤波方式,在滤波过程中能够有效地保护图像内的边缘信息。
函数使用在此不赘述。
edged = cv2.Canny(gray,30, 200)
contours = cv2.findContours(edged.copy(),cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
contours = imutils.grab_contours(contours)
contours = sorted(contours, key = cv2.contourArea,reverse=True)[:10]
screenCnt = None
放一张笔记照片吧,不想改格式了:
# 载入显示库
def bgr8_to_jpeg(value, quality=75):
return bytes(cv2.imencode('.jpg',value)[1])
import ipywidgets.widgets as widgets
from IPython.display import display
imagecar = widgets.Image(format='jpeg', width=600, height=400)
imageCropped = widgets.Image(format='jpeg', width=600, height=400)
display(imagecar)
display(imageCropped)
for c in contours:
peri = cv2.arcLength(c,-True)
approx = cv2.approxPolyDP(c, 0.018 * peri, True)
if len(approx) == 4:
screenCnt=approx
break
if screenCnt is None:
detected = 0
print ("No contour detected")
else:
detected = 1
if detected == 1:
cv2.drawContours(img, (screenCnt), -1, (0, 0, 255), 3)
mask = np.zeros(gray.shape,np.uint8)
new_image = cv2.drawContours(mask,[screenCnt],0,255,-1,)
new_image = cv2.bitwise_and(img,img,mask=mask)
(x, y) = np.where(mask == 255)
(topx, topy) = (np.min(x),np.min(y))
(bottomx, bottomy) = (np.max(x),np.max(y))
Cropped = gray[topx:bottomx+1,topy:bottomy+1]
text = pytesseract.image_to_string(Cropped, config='--psm 11')
print("programming_fever's License Plate Recognition\n")
print("Detected license plate Number is:",text)
img = cv2.resize(img,(500,300))
Cropped = cv2.resize(Cropped,(400,200))
# 显示图像
imagecar.value = bgr8_to_jpeg(img)
imageCropped.value = bgr8_to_jpeg(Cropped)
-
对提取到的轮廓进行处理,找到包含4个顶点的轮廓,表示可能是车牌的轮廓。
-
如果没有检测到符合条件的轮廓,则打印"No contour detected"。
-
如果检测到符合条件的轮廓,则在原图像上绘制出车牌的轮廓。
-
创建一个掩模(mask)来提取车牌区域,并将车牌区域提取出来。
-
使用Tesseract OCR库识别车牌区域中的文字。
-
打印出识别到的车牌号码。
QR-code二维码检测
安装Pyzbar
sudo pip3 install pyzbar
应用实例-二维码识别
# 载入必要的库
import cv2
import numpy as np
from pyzbar import pyzbar
import imutils
import sys
import time
# 摄像头初始化
import libcamera
from picamera2 import Picamera2
picamera = Picamera2()
config = picamera.create_preview_configuration(main={"format": "RGB888", "size": (320,240)},
raw={"format": "SRGGB12", "size": (1920, 1060)})
config["transform"] = libcamera.Transform(hflip=0,vflip=1)
picamera.configure(config)
picamera.start()
# 实时显示图像 没加q键退出
def Video_display():
while True:
frame1 = picamera.capture_array()
frame = imutils.resize(frame1,width = 400)
barcodes = pyzbar.decode(frame)
for barcode in barcodes:
(x, y, w, h) = barcode.rect
cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 0, 255), 2)
barcodeData = barcode.data.decode("utf-8")
barcodeType = barcode.type
text="{}({})".format(barcodeData, barcodeType)
cv2.putText(frame, text, (x, y + 20),cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 2)
print(text)
QR_img.value = bgr8_to_jpeg(frame)
Video_display()
二维码生成
网格地图视觉对齐
功能背景:在网格地图(白底黑格)中利用视觉去对齐,发送偏移标志给电机控制芯片,进行视觉上的对齐。
# OpenCV
import time
import numpy as np
import cv2
import imutils
# QR-code Tesseract
from pyzbar import pyzbar
import pytesseract
# Angle
import math
# UART
import serial
# Camera
import libcamera
from picamera2 import Picamera2
# 线程相关
import threading
import ctypes
import inspect
# 线程结束代码
def _async_raise(tid, exctype):
tid = ctypes.c_long(tid)
if not inspect.isclass(exctype):
exctype = type(exctype)
res = ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, ctypes.py_object(exctype))
if res == 0:
raise ValueError("invalid thread id")
elif res != 1:
ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, None)
raise SystemError("PyThreadState_SetAsyncExc failed")
def stop_thread(thread):
_async_raise(thread.ident, SystemExit)
def bgr8_to_jpeg(value, quality=75):
return bytes(cv2.imencode('.jpg', value)[1])
ser = serial.Serial(port = "/dev/ttyAMA0", baudrate = 115200)
# 摄像头初始化
def Camera_Init():
global picamera
picamera = Picamera2()
config = picamera.create_preview_configuration(main={"format": "RGB888"})
config["transform"] = libcamera.Transform(hflip=0, vflip=1)
picamera.configure(config)
picamera.start()
# 摄像头预览 q键退出
def Video_Demo():
while True:
video_demo = picamera.capture_array()
# cv2.line(video_demo, (0, 200), (640, 200), (255, 0, 0), 3)
cv2.imshow("preview", video_demo)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cv2.destroyAllWindows()
# 二维码识别 框选二维码 显示二维码信息 print二维码信息 q键退出
def Video_QRCODE():
while True:
frame = picamera.capture_array()
barcodes = pyzbar.decode(frame)
for barcode in barcodes:
(x, y, w, h) = barcode.rect
cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 0, 255), 2)
barcodeData = barcode.data.decode("utf-8")
barcodeType = barcode.type
text = "{}({})".format(barcodeData, barcodeType) # 图像format格式:QRcode信息(类型)
cv2.putText(frame, text, (x, y + 20), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 2) # 在长方形指定位置显示text
# print(text)
ser.write(text)
cv2.imshow("QR-code", frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cv2.destroyAllWindows()
# 仓库A1识别
def storehouse_detect():
while True:
image = picamera.capture_array()
cv2.bilateralFilter(image, 13, 15, 15)
cv2.imshow("storehouse", image)
if cv2.waitKey(1) & 0xFF == ord('a'):
text = pytesseract.image_to_string(image)
print(text)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cv2.destroyAllWindows()
def UART_With_STM32():
global data
data = [1,2]# need to use it !!
while True:
data = ser.read(2)
data = data[0:].decode()
def Follow_Lines():
global data
while True:
frame = picamera.capture_array()
frame = frame[240:480,0:640]
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
ret, dst = cv2.threshold(gray, 0, 255, cv2.THRESH_OTSU)
dst = cv2.bitwise_not(dst)
cv2.imshow("dst",dst)
lines = cv2.HoughLinesP(dst, 1, math.pi / 180, 100, minLineLength=400, maxLineGap=5)
if lines is not None:
for line in lines:
x1, y1, x2, y2 = line[0]
# Calculate angle between line and horizontal line
angle = math.degrees(math.atan2(y2 - y1, x2 - x1))
if(abs(angle) < 45):
cv2.putText(frame, f'Angle: {angle}', (10, 50), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)
cv2.line(frame, (x1, y1), (x2, y2), (0, 0, 255), 2)
if (data == "ok"):
data = [1,2]
if angle > 0.5:
ser.write(b'2')
print("2")
elif angle < -0.5:
ser.write(b'1')
print("1")
else:
ser.write(b'0')
print("0")
cv2.imshow('Frame', frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cv2.destroyAllWindows()
if __name__ == '__main__':
Camera_Init()
uart = threading.Thread(target=UART_With_STM32)
follow= threading.Thread(target = Follow_Lines)
uart.start()
follow.start()
多线程应用
串口函数需要一直运行,因树莓派5CPU为多核完全可以多线程或多进程,于是就没有选择使用RPi下的中断处理函数了,具体我也不了解树莓派的中断是如何使用的。由于Python的mutiprocessing和threading库太好用了,于是串口等待消息的函数就选择新建线程进行等待操作。
在此简要说明threading多线程操作库的使用方法:
-
导入线程模块,threading
-
通过线程类创建线程对象:线程对象 = threading.Thread(target = 任务名/函数名)
-
启动线程执行任务,线程对象.start()
uart = threading.Thread(target=UART_With_STM32)
follow= threading.Thread(target = Follow_Lines)
uart.start()
follow.start()
主线程会等待所有的子线程执行结束后再结束
如果需要守护主线程(若主线程结束,不等待子线程,子线程全部直接结束),有两种方法
-
创建子线程时新加参数daemon,令其为True:
threading_object = threading.Thread(target = f_name, daemon = True)
-
与多进程中守护主进程方法类似,在开启子线程之前:
threading_object.setDaemon(True)
与STM32串口通信
对于树莓派的硬件串口的使用
树莓派有2个串口,分别是硬件串口(/dev/ttyAMA0)和mini串口
-
硬件串口有单独的波特率时钟源,推荐使用。
-
mini串口性能差,功能简单,并且没有波特率专用的时钟源而是由CPU内核时钟提供,一般不使用。
对于树莓派5的硬件串口的使用,可以参考:初学树莓派——(五)树莓派串口收发(硬件串口)-CSDN博客
树莓派5与4B在硬件串口上基本相同,附树莓派5GPIO引脚:
注意:
-
安装后minicom运行:终端输入
minicom -D /dev/ttyAMA0 -b 115200
打开; 退出minicom:ctrl+A >> Z >> X
-
minicom是树莓派的串口助手,就像每一个嵌入式 / 硬件方面的工程师或学生Windows电脑中的串口助手,只不过minicom没有那么多丰富的功能,它只是一个可视化界面,既可以发送数据也可以接收数据。但要注意使用minicom时,窗口显示的是接收到的数据,发送的数据是不会显示在minicom上的。 例如,将树莓派的硬件串口通过USB转串口模块连接个人PC电脑后,树莓派端在minicom输入的内容直接可以在电脑端的串口助手显示,而树莓派端的minicom显示的则是电脑端发送的数据!
-
minicom在运行时,会抢占串口通道,导致程序代码的堵塞。进行代码上的串口操作时一定要关闭minicom。
-
UART_With_STM32()函数:data不需要手动清除,可以把它理解成一次性的,data不会重复被操作:例如,STM32每10s向树莓派发送数据“Hello”,而data变量在树莓派的某个程序中每秒被调用判断10次,树莓派在两次接收data数据间,只会进入一次data判断的程序,不会进入100次!
-
由于python与C不同,global data之后需要先使用一下data,代表在内存中开辟了这样的一个空间;如果没有data = [1,2]这一行,程序在接收到串口数据之后会报错!我这里是因为STM32会给树莓派传“ok”,于是data = [1,2],具体根据自己的项目要求。
# UART
import serial
ser = serial.Serial(port = "/dev/ttyAMA0", baudrate = 115200) # 端口/dev/ttyAMA0,波特率115200
ser.write(b'Hello World') # 发送数据
# 接收数据函数 一直等待接收
def UART_With_STM32():
global data # 以便此变量可以在其他函数中使用,于是将其定义为全局变量。
data = [1,2] # 重要
while True:
data = ser.read(2)
data = data[0:].decode() # 将data列表从头到尾解码
霍夫变换计算角度
cv2.HoughLines()函数是在二值图像中查找直线段。
函数参数:
HoughLinesP(image, rho, theta, threshold, lines=None, minLineLength=None, maxLineGap=None)
-
image: 必须是二值化图像。推荐使用Canny边缘检测图像。
-
rho:线段以?像素为单位的距离精度,?推荐1即可。
-
theta: 角度精度,推荐pi/180,pi为圆周率,math库下。
-
threshod:超过设定阈值才被检测出线段,值越大,基本上意味着检出的线段越长,检出的线段个数越少。推荐100,可调。
-
lines:我也不知道什么用:)
-
minLineLength:线段以像素为单位的最小长度,根据应用场景设置,可以多多尝试。
-
maxLineGap:同一方向上两条线段判定为一条线段的最大允许间隔,超过设定值,则把两条线段当成一条线段,值越大,允许线段上的断裂越大,越有可能检出潜在的直线段。根据项目要求吧,比如计算角度的霍夫直线操作,就可以把它设小点。
此代码功能:定性的传达偏左/右 or 对齐 的信息,定量的没做,因为小车用了PID再加上时间紧迫的原因,没有定量处理。
# 网格视觉对齐 Q键退出
def Follow_Lines():
global data
while True:
frame = picamera.capture_array()
# frame = frame[240:480,0:640] 选取画面的指定区域进行操作,以免画面的顶部的一些画面影响直线寻找
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) # 灰度图
ret, dst = cv2.threshold(gray, 0, 255, cv2.THRESH_OTSU) # 大津法二值化
dst = cv2.bitwise_not(dst) # 颜色反转 以便霍夫直线检测
# cv2.imshow("dst",dst) 显示原图
lines = cv2.HoughLinesP(dst, 1, math.pi / 180, 100, minLineLength=400, maxLineGap=5)
# 别忘了导入math库,
if lines is not None:
for line in lines:
x1, y1, x2, y2 = line[0]
# 利用斜率计算角度
angle = math.degrees(math.atan2(y2 - y1, x2 - x1))
if(abs(angle) < 45): # 只招对于画面而言|斜率| < 45°的直线。
# 显示角度并画线
cv2.putText(frame, f'Angle: {angle}', (10, 50), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)
cv2.line(frame, (x1, y1), (x2, y2), (0, 0, 255), 2)
if (data == "ok"): # 此if下的print是为了方便调试,可以知道视觉辅助对齐时,此时车是否对正了。
data = [1,2]
if angle > 0.5:
ser.write(b'2')
print("2")
elif angle < -0.5:
ser.write(b'1')
print("1")
else:
ser.write(b'0')
print("0")
cv2.imshow('Frame', frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cv2.destroyAllWindows()
结语
此文写的比较急,有些程序代码的效果图没放进去,其实是我当初学的时候拍的照片不小心删除找不到了,想学的可以自行尝试。此文也是个人分享。