<h1 style="font-size: 32px; font-weight: bold; border-bottom: 2px solid rgb(204, 204, 204); padding: 0px 4px 0px 0px; text-align: left; margin: 0px 0px 10px;">图像上的算术运算</h1><p style="margin-top: 0px; margin-bottom: 10px; white-space: normal; padding: 0px; list-style: none; border: 0px; overflow-wrap: break-word; word-break: break-all; line-height: 1.5em; font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Noto Sans CJK SC", "WenQuanYi Micro Hei", Arial, sans-serif; color: rgb(51, 51, 51); box-sizing: border-box;">当前系列所有demo下载地址:</p><p style="margin-top: 0px; margin-bottom: 10px; white-space: normal; padding: 0px; list-style: none; border: 0px; overflow-wrap: break-word; word-break: break-all; line-height: 1.5em; font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Noto Sans CJK SC", "WenQuanYi Micro Hei", Arial, sans-serif; color: rgb(51, 51, 51); box-sizing: border-box;"><a href="https://github.com/GaoRenBao/OpenCv4-Demo" target="_blank" style="margin: 0px; padding: 0px; list-style: none; border: 0px; color: rgb(0, 102, 0); transition-duration: 0.2s; transition-property: opacity; outline: none; opacity: 0.8;">https://github.com/GaoRenBao/OpenCv4-Demo</a></p><p style="margin-top: 0px; margin-bottom: 10px; text-wrap: wrap; padding: 0px; list-style: none; border: 0px; overflow-wrap: break-word; word-break: break-all; line-height: 1.5em; font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Noto Sans CJK SC", "WenQuanYi Micro Hei", Arial, sans-serif; box-sizing: border-box; color: rgb(51, 51, 51);">不同编程语言对应的OpenCv版本以及<span style="">开发环境</span>信息如下:<span style="font-family: Calibri; font-size: 14px;"> </span></p><table border="1" style="text-wrap: wrap; border-right: none; border-bottom: none; border-image: initial; border-left: 1px solid rgb(102, 102, 102); border-top: 1px solid rgb(102, 102, 102);"><tbody><tr class="firstRow"><td width="81" valign="top" style="border-color: windowtext rgb(102, 102, 102) rgb(102, 102, 102) windowtext; border-bottom-width: 1px; border-bottom-style: solid; border-right-width: 1px; border-right-style: solid; padding: 5px;"><p style="text-align: center;"><strong><span style="font-family: 宋体; font-size: 14px;">语言</span></strong></p></td><td width="223" valign="top" style="border-color: windowtext rgb(102, 102, 102) rgb(102, 102, 102) windowtext; border-bottom-width: 1px; border-bottom-style: solid; border-right-width: 1px; border-right-style: solid; padding: 5px;"><p style="text-align: center;"><strong><span style="font-family: 宋体; font-size: 14px;"><span style="font-family: Calibri;">OpenCv</span>版本</span></strong></p></td><td width="242" valign="top" style="border-color: windowtext rgb(102, 102, 102) rgb(102, 102, 102) windowtext; border-bottom-width: 1px; border-bottom-style: solid; border-right-width: 1px; border-right-style: solid; padding: 5px;"><p style="text-align: center;"><strong><span style="font-family: 宋体; font-size: 14px;"><span style="font-family: Calibri;">IDE</span></span></strong></p></td></tr><tr><td width="81" valign="top" style="border-top: none; border-left-color: windowtext; border-bottom: 1px solid rgb(102, 102, 102); border-right: 1px solid rgb(102, 102, 102); padding: 5px;"><p><span style="font-family: 宋体; font-size: 14px;"><span style="font-family: Calibri;">C#</span></span></p></td><td width="223" valign="top" style="border-top: none; border-left-color: windowtext; border-bottom: 1px solid rgb(102, 102, 102); border-right: 1px solid rgb(102, 102, 102); padding: 5px;"><p><span style="font-family: Calibri; letter-spacing: 0px;">OpenCvSharp4.4.8.0.20230708</span></p></td><td width="242" valign="top" style="border-top: none; border-left-color: windowtext; border-bottom: 1px solid rgb(102, 102, 102); border-right: 1px solid rgb(102, 102, 102); padding: 5px;"><p><span style="font-family: 宋体; font-size: 14px;"><span style="font-family: Calibri;">Visual Studio 2022</span></span></p></td></tr><tr><td width="81" valign="top" style="border-top: none; border-left-color: windowtext; border-bottom: 1px solid rgb(102, 102, 102); border-right: 1px solid rgb(102, 102, 102); padding: 5px;"><p><span style="font-family: 宋体; font-size: 14px;"><span style="font-family: Calibri;">C++</span></span></p></td><td width="223" valign="top" style="border-top: none; border-left-color: windowtext; border-bottom: 1px solid rgb(102, 102, 102); border-right: 1px solid rgb(102, 102, 102); padding: 5px;"><p><span style="font-family: 宋体; letter-spacing: 0px;"><span style="font-family: Calibri;">O</span></span><span style="font-family: Calibri; letter-spacing: 0px;">pen</span><span style="font-family: 宋体; letter-spacing: 0px;"><span style="font-family: Calibri;">C</span></span><span style="font-family: Calibri; letter-spacing: 0px;">v-4.5.5-vc14_vc15</span></p></td><td width="242" valign="top" style="border-top: none; border-left-color: windowtext; border-bottom: 1px solid rgb(102, 102, 102); border-right: 1px solid rgb(102, 102, 102); padding: 5px;"><p><span style="font-family: 宋体; font-size: 14px;"><span style="font-family: Calibri;">Visual Studio 2022</span></span></p></td></tr><tr><td width="81" valign="top" style="border-top: none; border-left-color: windowtext; border-bottom: 1px solid rgb(102, 102, 102); border-right: 1px solid rgb(102, 102, 102); padding: 5px;"><p><span style="font-family: 宋体; font-size: 14px;"><span style="font-family: Calibri;">Python</span></span></p></td><td width="223" valign="top" style="border-top: none; border-left-color: windowtext; border-bottom: 1px solid rgb(102, 102, 102); border-right: 1px solid rgb(102, 102, 102); padding: 5px;"><p><span style="font-family: 宋体; font-size: 14px;"><span style="font-family: Calibri;">OpenCv-Python (4.6.0.66)</span></span></p></td><td width="242" valign="top" style="border-top: none; border-left-color: windowtext; border-bottom: 1px solid rgb(102, 102, 102); border-right: 1px solid rgb(102, 102, 102); padding: 5px;"><p><span style="font-family: 宋体; font-size: 14px;"><span style="font-family: Calibri;">PyCharm Community Edition 2022.1.3</span></span></p></td></tr></tbody></table><p><span style="text-wrap: wrap;"></span></p><p style="margin-top: 0px; margin-bottom: 10px; white-space: normal; box-sizing: border-box; overflow-wrap: break-word; word-break: break-all; line-height: 1.5em; font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Noto Sans CJK SC", "WenQuanYi Micro Hei", Arial, sans-serif; color: rgb(51, 51, 51); "><a target="_blank" href="https://pan.baidu.com/s/1T9xl4KOBvU1g7AyDfPWlpw" style="box-sizing: border-box; color: rgb(0, 102, 0); transition-duration: 0.2s; transition-property: opacity; background-color: transparent; outline: none;"></a></p><p style="white-space: normal;"><br/></p><p style="white-space: normal;">我们测试用的图片如下:</p><p style="white-space: normal;"><img src="/upload/image/6383382431477390899031122.jpg" title="subtract1.jpg" alt="subtract1.jpg"/></p><p style="white-space: normal;"><img src="/upload/image/6383382432058276035388984.jpg" title="subtract2.jpg" alt="subtract2.jpg"/></p><p style="white-space: normal;"><br/></p><p style="white-space: normal;">demo1:图像的减法运算</p><p style="white-space: normal;"><img src="/upload/image/6379740480262779687126986.jpg" title="微信截图_20220829211926.jpg" alt="微信截图_20220829211926.jpg"/></p><p style="white-space: normal;"><br/></p><p style="white-space: normal;"><span style="text-wrap: wrap;">demo2:</span><span style="text-wrap: wrap;">通过图像相减,查找扑克牌位置</span></p><p style="white-space: normal;"><img src="/upload/image/6379740496557318492161484.jpg" title="微信截图_20220829212231.jpg" alt="微信截图_20220829212231.jpg"/></p><p style="white-space: normal;"><br/></p><p style="white-space: normal;"><span style="text-wrap: wrap;">demo3:</span><span style="text-wrap: wrap;">通过图像相减,凸显扑克牌位置</span></p><p style="white-space: normal;"><span style="text-wrap: wrap;"><img src="/upload/image/6379740505474043356989160.jpg" title="QQ截图20220829212406.jpg" alt="QQ截图20220829212406.jpg"/></span></p><p style="white-space: normal;"><br/></p><p style="white-space: normal;"><span style="text-wrap: wrap;"><span style="text-wrap: wrap;">demo4:</span><span style="text-wrap: wrap;">调用摄像头,通过图像相减,标记扑克牌位置</span></span></p><p style="white-space: normal;"><img src="/upload/image/6379935004700128506366981.gif" title="6379740599035057869137285 (1).gif" alt="6379740599035057869137285 (1).gif"/></p><p style="white-space: normal;"><br/></p><p style="white-space: normal;"><strong style="margin: 0px; padding: 0px; list-style: none; border: 0px; color: rgb(51, 51, 51); font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Noto Sans CJK SC", "WenQuanYi Micro Hei", Arial, sans-serif; "><span style="font-size: 16px; font-style: italic; font-weight: bold; color: rgb(51, 153, 204); line-height: 18px;">C#版本运行代码如下:</span></strong></p><pre class="brush:c#;toolbar:false">using OpenCvSharp;
using System;
namespace demo
{
internal class Program
{
static void Main(string[] args)
{
demo1();
//demo2();
//demo3();
//demo4();
}
public static void myImshow(string name, Mat img)
{
Mat temp = new Mat();
Cv2.Resize(img, temp, new Size(img.Cols * 1, img.Rows * 1), 0, 0, InterpolationFlags.Nearest);
Cv2.ImShow(name, temp);
}
#region 图像相减1:图像的减法运算
public static void demo1()
{
Mat img1 = Cv2.ImRead("../../../images/subtract1.jpg", 0); // 灰度图
Mat img2 = Cv2.ImRead("../../../images/subtract2.jpg", 0);
myImshow("subtract1", img1);
myImshow("subtract2", img2);
Mat st = img2 - img1;
myImshow("after subtract", st);
Mat threshold = new Mat();
Cv2.Threshold(st, threshold, 50, 255, ThresholdTypes.Binary);
myImshow("after threshold", threshold);
Cv2.WaitKey(0);
}
#endregion
#region 图像相减2:通过图像相减,查找扑克牌位置
public static void demo2()
{
Mat img1 = Cv2.ImRead("../../../images/subtract1.jpg", 0);
Mat img22 = Cv2.ImRead("../../../images/subtract2.jpg");
Mat img2 = new Mat();
Cv2.CvtColor(img22, img2, ColorConversionCodes.BGR2GRAY);
Mat st = new Mat();
Cv2.Subtract(img2, img1, st);
//Cv2.Subtract(img1, img2, st); // 相反
// 把小于5的像素点设为0
for (int i = 0; i < st.Rows; ++i)
{
for (int j = 0; j < st.Cols; ++j)
{
byte rgb = st.At<byte>(i, j);
if (rgb > 0 && rgb <= 5)
st.Set(i, j, 0);
}
}
Mat threshold = new Mat();
Cv2.Threshold(st, threshold, 50, 255, ThresholdTypes.Binary);
Point[][] contours = new Point[][] { };
HierarchyIndex[] hierarcy;
Cv2.FindContours(threshold, out contours, out hierarcy, RetrievalModes.Tree, ContourApproximationModes.ApproxSimple, null);
//List<(int, double)> areas = new List<(int, double)>();
//for (int i = 0; i < contours.Length; i++)
//{
// areas.Add((i, Cv2.ContourArea(contours[i])));
//}
//// 降序排序
//var a2 = areas.OrderByDescending(x => x.Item2).ToList();
//foreach(var are in a2)
//{
// if (are.Item2 < 100)
// continue;
// Cv2.DrawContours(img22, contours, are.Item1, new Scalar(0, 0, 255), 3);
// myImshow("approxPolyDP", img22);
// Cv2.WaitKey(0);
//}
// TODO 截取原图,把长方形纠正
Point[] cnt = contours[0];
Point[] hull = Cv2.ConvexHull(cnt);
double epsilon = 0.001 * Cv2.ArcLength(hull, true);
Point[] simplified_cnt = Cv2.ApproxPolyDP(hull, epsilon, true);
epsilon = 0.1 * Cv2.ArcLength(cnt, true);
Point[] approx = Cv2.ApproxPolyDP(cnt, epsilon, true);
Point[][] approxs = new Point[][] { approx };
Cv2.DrawContours(img22, approxs, 0, new Scalar(255, 0, 0), 3);
myImshow("approxPolyDP", img22);
Cv2.WaitKey(0);
}
#endregion
#region 图像相减3:通过图像相减,凸显扑克牌位置
// returns just the difference of the two images
public static Mat diff(Mat img, Mat img1)
{
Mat diff = new Mat();
Cv2.Absdiff(img, img1, diff);
return diff;
}
// removes the background but requires three images
public static Mat diff_remove_bg(Mat img0, Mat img, Mat img1)
{
Mat d1 = diff(img0, img);
Mat d2 = diff(img, img1);
Mat a = new Mat();
Cv2.BitwiseAnd(d1, d2, a);
return a;
}
public static void demo3()
{
Mat img1 = Cv2.ImRead("../../../images/subtract1.jpg", 0);
Mat img2 = Cv2.ImRead("../../../images/subtract2.jpg", 0);
myImshow("subtract1", img1);
myImshow("subtract2", img2);
Mat st = diff_remove_bg(img2, img1, img2);
myImshow("after subtract", st);
Cv2.WaitKey(0);
}
#endregion
#region 调用摄像头,通过图像相减,标记扑克牌位置
public static void demo4()
{
VideoCapture Cap = new VideoCapture();
Cap.Open(0);
// 判断摄像头是否成功打开
if (!Cap.IsOpened())
{
Console.WriteLine("摄像头打开失败.");
return;
}
/* opencv3 */
//Cap.Set(CaptureProperty.FrameWidth, 1280); // 设置采集的图像宽度
//Cap.Set(CaptureProperty.FrameHeight, 720); // 设置采集的图像高度
/* opencv4 */
Cap.Set(VideoCaptureProperties.FrameWidth, 1280); // 设置采集的图像宽度
Cap.Set(VideoCaptureProperties.FrameHeight, 720); // 设置采集的图像高度
//Cap.Set(VideoCaptureProperties.Exposure, 0); // 设置曝光值
Mat bgimg0 = new Mat();
Mat bgimg = new Mat();
int frame_no = 10; // 第10帧稳定图像
while (frame_no > 0)
{
if (Cap.Read(bgimg0))
frame_no--;
}
Cv2.CvtColor(bgimg0, bgimg, ColorConversionCodes.BGR2GRAY);
Mat frame = new Mat();
Mat gray = new Mat();
Mat st = new Mat();
Mat img = new Mat();
while (true)
{
if (Cap.Read(frame))
{
Cv2.CvtColor(frame, gray, ColorConversionCodes.BGR2GRAY);
Cv2.Subtract(gray, bgimg, st);
Mat threshold = new Mat();
Cv2.Threshold(st, threshold, 50, 255, ThresholdTypes.Binary);
Point[][] contours = new Point[][] { };
HierarchyIndex[] hierarcy;
Cv2.FindContours(threshold, out contours, out hierarcy, RetrievalModes.Tree, ContourApproximationModes.ApproxSimple, null);
Cv2.DrawContours(st, contours, -1, new Scalar(255, 255, 255), 3);
img = st;
for (int i = 0; i < contours.Length; i++)
{
var area = Cv2.ContourArea(contours[i]);
if (area < 200)
continue;
double peri = Cv2.ArcLength(contours[i], true);
Point[] approx = Cv2.ApproxPolyDP(contours[i], 0.04 * peri, true);
if (approx.Length == 4)
{
Rect rect = Cv2.BoundingRect(approx);
Cv2.Rectangle(frame, new Point(rect.X, rect.Y),
new Point(rect.X + rect.Width, rect.Y + rect.Height),
new Scalar(0, 0, 255), 2);
}
}
// TODO 对比前几/十几帧,新放一张扑克,知道是那张
// 等待图像稳定,不放牌后,再计算
myImshow("frame", frame);
myImshow("subtract", img);
myImshow("threshold", threshold);
Cv2.WaitKey(1);
}
}
}
#endregion
}
}</pre><p style="white-space: normal;"><br/></p><p style="white-space: normal;"><strong style="margin: 0px; padding: 0px; list-style: none; border: 0px; color: rgb(51, 51, 51); font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Noto Sans CJK SC", "WenQuanYi Micro Hei", Arial, sans-serif; "><strong style="margin: 0px; padding: 0px; list-style: none; border: 0px;"><span style="font-size: 16px; font-style: italic; font-weight: bold; color: rgb(51, 153, 204); line-height: 18px;">C++<strong style="margin: 0px; padding: 0px; list-style: none; border: 0px;">版本运行代码如下:</strong></span><strong style="margin: 0px; padding: 0px; list-style: none; border: 0px;"></strong></strong></strong></p><p style="white-space: normal;"><strong style="margin: 0px; padding: 0px; list-style: none; border: 0px; color: rgb(51, 51, 51); font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Noto Sans CJK SC", "WenQuanYi Micro Hei", Arial, sans-serif; "><strong style="margin: 0px; padding: 0px; list-style: none; border: 0px;"><strong style="margin: 0px; padding: 0px; list-style: none; border: 0px;"></strong></strong></strong></p><pre class="brush:cpp;toolbar:false;">#include <opencv2/opencv.hpp>
#include <opencv2/cvconfig.h>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <io.h>
#include <string>
#include <iostream>
using namespace cv;
using namespace std;
void myImshow(string name, Mat img)
{
Mat temp;
cv::resize(img, temp, Size(img.cols * 1, img.rows * 1), 0, 0, 3);
cv::imshow(name, temp);
}
// 图像相减1:图像的减法运算
void demo1()
{
Mat img1 = cv::imread("../images/subtract1.jpg", 0); // 灰度图
Mat img2 = cv::imread("../images/subtract2.jpg", 0); // 灰度图
myImshow("subtract1", img1);
myImshow("subtract2", img2);
Mat st = img2 - img1;
myImshow("after subtract", st);
Mat threshold;
cv::threshold(st, threshold, 50, 255, THRESH_BINARY);
myImshow("after threshold", threshold);
cv::waitKey(0);
}
// 图像相减2:通过图像相减,查找扑克牌位置
void demo2()
{
Mat img1 = cv::imread("../images/subtract1.jpg", 0);
Mat img22 = cv::imread("../images/subtract2.jpg");
Mat img2;
cv::cvtColor(img22, img2, COLOR_BGR2GRAY);
Mat st;
cv::subtract(img2, img1, st);
//cv::Subtract(img1, img2, st); // 相反
// 把小于5的像素点设为0
for (int i = 0; i < st.rows; ++i)
{
for (int j = 0; j < st.cols; ++j)
{
char rgb = st.at<char>(i, j);
if (rgb > 0 && rgb <= 5)
st.at<char>(i, j) = 0;
}
}
Mat threshold;
cv::threshold(st, threshold, 50, 255, THRESH_BINARY);
vector<vector<Point>> contours;
vector<Vec4i> hierarcy;
findContours(threshold, contours, hierarcy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0));
//for (int i = 0; i < contours.size(); i++)
//{
// int area = cv::contourArea(contours[i]);
// if (area < 100)
// continue;
// cv::drawContours(img22, contours, i, Scalar(0, 0, 255), 3);
// myImshow("approxPolyDP", img22);
// cv::waitKey(0);
//}
// TODO 截取原图,把长方形纠正
vector<Point> cnt = contours[0];
vector<Point> hull;
cv::convexHull(cnt, hull);
double epsilon = 0.001 * cv::arcLength(hull, true);
std::vector<cv::Point> simplified_cnt;
cv::approxPolyDP(hull, simplified_cnt, epsilon, true);
epsilon = 0.1 * cv::arcLength(cnt, true);
vector<Point> approx;
cv::approxPolyDP(cnt, approx, epsilon, true);
vector<vector<Point>> approxs;
approxs.push_back(approx);
cv::drawContours(img22, approxs, 0, Scalar(255, 0, 0), 3);
myImshow("approxPolyDP", img22);
cv::waitKey(0);
}
// returns just the difference of the two images
Mat diff(Mat img, Mat img1)
{
Mat diff;
cv::absdiff(img, img1, diff);
return diff;
}
// removes the background but requires three images
Mat diff_remove_bg(Mat img0, Mat img, Mat img1)
{
Mat d1 = diff(img0, img);
Mat d2 = diff(img, img1);
Mat a;
cv::bitwise_and(d1, d2, a);
return a;
}
// 图像相减3:通过图像相减,凸显扑克牌位置
void demo3()
{
Mat img1 = cv::imread("../images/subtract1.jpg", 0);
Mat img2 = cv::imread("../images/subtract2.jpg", 0);
myImshow("subtract1", img1);
myImshow("subtract2", img2);
Mat st = diff_remove_bg(img2, img1, img2);
myImshow("after subtract", st);
cv::waitKey(0);
}
// 调用摄像头,通过图像相减,标记扑克牌位置
void demo4()
{
VideoCapture cap;
cap.open(0);
if (!cap.isOpened())
{
cout << "摄像头打开失败." << endl;
while (true);
return;
}
//cap.set(CAP_PROP_FRAME_WIDTH, 1280); // 宽度
//cap.set(CAP_PROP_FRAME_HEIGHT, 720); // 高度
//cap.set(CAP_PROP_EXPOSURE, 0); // 曝光
Mat bgimg0;
Mat bgimg;
int frame_no = 10; // 第10帧稳定图像
while (frame_no > 0)
{
cap >> bgimg0;
if (!bgimg0.empty())
{
frame_no--;
}
}
cv::cvtColor(bgimg0, bgimg, COLOR_BGR2GRAY);
Mat frame;
Mat gray;
Mat st;
Mat img;
while (true)
{
cap >> frame;
if (!frame.empty())
{
cv::cvtColor(frame, gray, COLOR_BGR2GRAY);
cv::subtract(gray, bgimg, st);
Mat threshold;
cv::threshold(st, threshold, 50, 255, THRESH_BINARY);
vector<vector<Point>> contours;
vector<Vec4i> hierarcy;
findContours(threshold, contours, hierarcy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0));
cv::drawContours(st, contours, -1, Scalar(255, 255, 255), 3);
img = st;
for (int i = 0; i < contours.size(); i++)
{
int area = cv::contourArea(contours[i]);
if (area < 200)
continue;
double peri = cv::arcLength(contours[i], true);
vector<Point> approx;
cv::approxPolyDP(contours[i], approx, 0.04 * peri, true);
if (approx.size() == 4)
{
Rect rect = cv::boundingRect(approx);
cv::rectangle(frame, Point(rect.x, rect.y),
Point(rect.x + rect.width, rect.y + rect.height),
Scalar(0, 0, 255), 2);
}
}
// TODO 对比前几/十几帧,新放一张扑克,知道是那张
// 等待图像稳定,不放牌后,再计算
myImshow("frame", frame);
myImshow("subtract", img);
myImshow("threshold", threshold);
cv::waitKey(1);
}
}
}
int main()
{
demo1();
//demo2();
//demo3();
//demo4();
}</pre><p style="white-space: normal;"><strong style="margin: 0px; padding: 0px; list-style: none; border: 0px; color: rgb(51, 51, 51); font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Noto Sans CJK SC", "WenQuanYi Micro Hei", Arial, sans-serif; "><strong style="margin: 0px; padding: 0px; list-style: none; border: 0px;"><strong style="margin: 0px; padding: 0px; list-style: none; border: 0px;"></strong></strong></strong><br/></p><p style="white-space: normal;"><strong style="margin: 0px; padding: 0px; list-style: none; border: 0px; color: rgb(51, 51, 51); font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Noto Sans CJK SC", "WenQuanYi Micro Hei", Arial, sans-serif; "><strong style="margin: 0px; padding: 0px; list-style: none; border: 0px;"><span style="font-size: 16px; font-style: italic; font-weight: bold; color: rgb(51, 153, 204); line-height: 18px;">Python<strong style="margin: 0px; padding: 0px; list-style: none; border: 0px;">版本运行效果及代码如下:</strong></span></strong></strong></p><p style="white-space: normal;"></p><p style="white-space: normal;">demo1.py:图像的减法运算:</p><pre class="brush:python;toolbar:false">import cv2
# img1=cv2.imread('../images/subtract1.jpg')
img1 = cv2.imread('../images/subtract1.jpg', 0) # 灰度图
# img2=cv2.imread('../images/subtract2.jpg')
img2 = cv2.imread('../images/subtract2.jpg', 0)
cv2.imshow('subtract1', img1)
cv2.imshow('subtract2', img2)
#
st = img2 - img1
# st=img1-img2#相反
cv2.imshow('after subtract', st)
# 效果好一点
# ret,threshold=cv2.threshold(st,0, 127, cv2.THRESH_BINARY)
ret, threshold = cv2.threshold(st, 50, 255, cv2.THRESH_BINARY)
cv2.imshow('after threshold', threshold)
cv2.waitKey(0)</pre><p style="white-space: normal;"><br/></p><p>demo2.py:通过图像相减,查找扑克牌位置:</p><pre class="brush:python;toolbar:false">import cv2
import numpy as np
import matplotlib.pyplot as plt
# img1=cv2.imread('../images/subtract1.jpg')
img1 = cv2.imread('../images/subtract1.jpg', 0) # 灰度图
# img2=cv2.imread('../images/subtract2.jpg')
# img2 = cv2.imread('../images/subtract2.jpg', 0)
img22 = cv2.imread('../images/subtract2.jpg')
img2 = cv2.cvtColor(img22, cv2.COLOR_BGR2GRAY)
# cv2.imshow('subtract1', img1)
# cv2.imshow('subtract2', img2)
#
st = cv2.subtract(img2, img1)
# st = cv2.subtract(img1, img2)#相反
st[st <= 5] = 0 # 把小于20的像素点设为0
# cv2.imshow('after subtract', st)
'''
# 直方图,看看大部分像素集中在哪个区域
# plt.plot(st)
pxs = st.ravel()
pxs=[x for x in pxs if x>5]#20,10
plt.hist(pxs, 256, [0, 256])
plt.show()
'''
# 效果好一点
# ret,threshold=cv2.threshold(st,0, 127, cv2.THRESH_BINARY)
ret, threshold = cv2.threshold(st, 50, 255, cv2.THRESH_BINARY)
# cv2.imshow('after threshold', threshold)
contours, hierarchy = cv2.findContours(threshold, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
areas = list()
for i, cnt in enumerate(contours):
areas.append((i, cv2.contourArea(cnt)))
#
a2 = sorted(areas, key=lambda d: d[1], reverse=True)
'''
for i,are in a2:
if are <100:
continue
cv2.drawContours(img22, contours, i, (0, 0, 255), 3)
print(i,are)
cv2.imshow('drawContours',img22)
cv2.waitKey(0)
# cv2.destroyAllWindows()
'''
# TODO 截取原图,把长方形纠正
cnt = contours[0]
print(cnt)
hull = cv2.convexHull(cnt)
epsilon = 0.001 * cv2.arcLength(hull, True)
simplified_cnt = cv2.approxPolyDP(hull, epsilon, True)
epsilon = 0.1 * cv2.arcLength(cnt, True)
approx = cv2.approxPolyDP(cnt, epsilon, True)
print(approx)
cv2.drawContours(img22, [approx], 0, (255, 0, 0), 3)
cv2.imshow('approxPolyDP', img22)
cv2.waitKey(0)
exit(3)
# findHomography(srcPoints, dstPoints, method=None, ransacReprojThreshold=None, mask=None, maxIters=None, confidence=None)
# H = cv2.findHomography(srcPoints=cnt.astype('single'), dstPoints=np.array([[[0., 0.]], [[2150., 0.]], [[2150., 2800.]], [[0., 2800.]]]))
# M, mask = cv2.findHomography(src_pts, dst_pts, cv2.RANSAC, 5.0)
# now that we have our screen contour, we need to determine
# the top-left, top-right, bottom-right, and bottom-left
# points so that we can later warp the image -- we'll start
# by reshaping our contour to be our finals and initializing
# our output rectangle in top-left, top-right, bottom-right,
# and bottom-left order
pts = approx.reshape(4, 2)
rect = np.zeros((4, 2), dtype="float32")
# the top-left point has the smallest sum whereas the
# bottom-right has the largest sum
s = pts.sum(axis=1)
rect[0] = pts[np.argmin(s)]
rect[2] = pts[np.argmax(s)]
# compute the difference between the points -- the top-right
# will have the minumum difference and the bottom-left will
# have the maximum difference
diff = np.diff(pts, axis=1)
rect[1] = pts[np.argmin(diff)]
rect[3] = pts[np.argmax(diff)]
# multiply the rectangle by the original ratio
ratio = image.shape[0] / 300.0
rect *= ratio
# now that we have our rectangle of points, let's compute
# the width of our new image
(tl, tr, br, bl) = rect
widthA = np.sqrt(((br[0] - bl[0]) ** 2) + ((br[1] - bl[1]) ** 2))
widthB = np.sqrt(((tr[0] - tl[0]) ** 2) + ((tr[1] - tl[1]) ** 2))
# ...and now for the height of our new image
heightA = np.sqrt(((tr[0] - br[0]) ** 2) + ((tr[1] - br[1]) ** 2))
heightB = np.sqrt(((tl[0] - bl[0]) ** 2) + ((tl[1] - bl[1]) ** 2))
# take the maximum of the width and height values to reach
# our final dimensions
maxWidth = max(int(widthA), int(widthB))
maxHeight = max(int(heightA), int(heightB))
# construct our destination points which will be used to
# map the screen to a top-down, "birds eye" view
dst = np.array([
[0, 0],
[maxWidth - 1, 0],
[maxWidth - 1, maxHeight - 1],
[0, maxHeight - 1]], dtype="float32")
# calculate the perspective transform matrix and warp
# the perspective to grab the screen
M = cv2.getPerspectiveTransform(rect, dst)
warp = cv2.warpPerspective(img22, M, (maxWidth, maxHeight))
# final_image = cv2.warpPerspective(img22, H, (2150, 2800))
cv2.imshow('final_image', warp)
cv2.waitKey(0)</pre><p><br/></p><p>demo3.py:通过图像相减,凸显扑克牌位置:</p><pre class="brush:python;toolbar:false">"""
图像相减3.py:
3张图片
"""
import cv2
def diff(img, img1): # returns just the difference of the two images
return cv2.absdiff(img, img1)
def diff_remove_bg(img0, img, img1): # removes the background but requires three images
d1 = diff(img0, img)
d2 = diff(img, img1)
return cv2.bitwise_and(d1, d2)
# img1=cv2.imread('subtract1.jpg')
img1 = cv2.imread('subtract1.jpg', 0) # 灰度图
# img2=cv2.imread('subtract2.jpg')
img2 = cv2.imread('subtract2.jpg', 0)
cv2.imshow('subtract1', img1)
cv2.imshow('subtract2', img2)
st = diff_remove_bg(img2, img1,img2)
cv2.imshow('after subtract', st)
cv2.waitKey(0)</pre><p><br/></p><p>demo4.py:调用摄像头,通过图像相减,标记扑克牌位置:</p><pre class="brush:python;toolbar:false">import cv2
import numpy as np
def myImshow(name,img):
imgH = img.shape[0]
imgW = img.shape[1]
temp = cv2.resize(img, ((int)(imgW*0.5), (int)(imgH*0.5)), interpolation = cv2.INTER_NEAREST)
cv2.imshow(name, temp)
# 外部摄像头默认好像是CAP_MSMF格式,会导致摄像头无法打开,设置成CAP_DSHOW就可以了
cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)
#ret = cap.set(3, 640)
#ret = cap.set(4, 480)
ret = cap.set(cv2.CAP_PROP_FRAME_WIDTH,1280) # 设置图像宽
ret = cap.set(cv2.CAP_PROP_FRAME_HEIGHT,720) # 设置图像高
ret = cap.set(cv2.CAP_PROP_EXPOSURE, 0) # 设置曝光值
cap.read()
cap.read()
cap.read()
cap.read()
cap.read()
cap.read()
cap.read()
cap.read()
cap.read()
cap.read()
cap.read()
cap.read()
cap.read()
cap.read()
'''
cal=[cap.read()[1] for x in range(20)]
#mean 直接的加减是不行的
# bgimg0=np.mean(np.sum(cal))
# bgimg0=np.average(cal)
# bgimg0=np.mean(cal)
nps1=sum(cal)
mean1=nps1/len(cal)
# mean1[mean1<0]=0
# mean1[mean1>255]=255
cv2.imshow('bgimg', mean1)
cv2.waitKey(0)
exit(3)
'''
frame_no = 100
# cap.set(1, frame_no)#第10帧
ret, bgimg0 = cap.read() # 背景
bgimg = cv2.cvtColor(bgimg0, cv2.COLOR_BGR2GRAY)
myImshow('bgimg' + str(frame_no), bgimg0)
# cv2.imwrite('desk_bgimg.jpg',bgimg)
while cap.isOpened():
ret, frame = cap.read() # TODO 图像稳定
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
#
st = cv2.subtract(gray, bgimg)
# st = cv2.subtract(img1, img2)#相反
# st[st <= 5] = 0 # 把小于20的像素点设为0
ret, threshold = cv2.threshold(st, 50, 255, cv2.THRESH_BINARY)
contours, hierarchy = cv2.findContours(threshold, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
print("contours size: ", len(contours))
# img = cv2.drawContours(st, contours, -1, (0, 0, 0), 13)
img = cv2.drawContours(st, contours, -1, (255, 255, 255), 3)
#
for cnt in contours:
area = cv2.contourArea(cnt)
if area < 200:
continue
peri = cv2.arcLength(cnt, True)
approx = cv2.approxPolyDP(cnt, 0.04 * peri, True)
if len(approx) == 4:
(x, y, w, h) = cv2.boundingRect(approx)
cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 0, 255), 2)
# TODO 对比前几/十几帧,新放一张扑克,知道是那张
# 等待图像稳定,不放牌后,再计算
myImshow("frame", frame)
myImshow("subtract", img)
# cv2.moveWindow("subtract", y=bgimg.shape[0], x=0)
myImshow('threshold', threshold)
# cv2.moveWindow("threshold", x=bgimg.shape[1], y=0)
key = cv2.waitKey(delay=1)
if key == ord("q"):
break
elif key == ord("s"):
cv2.imwrite('poker-threshold.jpg', threshold)
cv2.destroyAllWindows()</pre><p><br/></p>
图像上的算术运算 当前系列所有demo下载地址:
https://github.com/GaoRenBao/OpenCv4-Demo
不同编程语言对应的OpenCv版本以及开发环境 信息如下:
语言
OpenCv 版本
IDE
C#
OpenCvSharp4.4.8.0.20230708
Visual Studio 2022
C++
O pen C v-4.5.5-vc14_vc15
Visual Studio 2022
Python
OpenCv-Python (4.6.0.66)
PyCharm Community Edition 2022.1.3
我们测试用的图片如下:
demo1:图像的减法运算
demo2: 通过图像相减,查找扑克牌位置
demo3: 通过图像相减,凸显扑克牌位置
demo4: 调用摄像头,通过图像相减,标记扑克牌位置
C#版本运行代码如下:
using OpenCvSharp;
using System;
namespace demo
{
internal class Program
{
static void Main(string[] args)
{
demo1();
//demo2();
//demo3();
//demo4();
}
public static void myImshow(string name, Mat img)
{
Mat temp = new Mat();
Cv2.Resize(img, temp, new Size(img.Cols * 1, img.Rows * 1), 0, 0, InterpolationFlags.Nearest);
Cv2.ImShow(name, temp);
}
#region 图像相减1:图像的减法运算
public static void demo1()
{
Mat img1 = Cv2.ImRead("../../../images/subtract1.jpg", 0); // 灰度图
Mat img2 = Cv2.ImRead("../../../images/subtract2.jpg", 0);
myImshow("subtract1", img1);
myImshow("subtract2", img2);
Mat st = img2 - img1;
myImshow("after subtract", st);
Mat threshold = new Mat();
Cv2.Threshold(st, threshold, 50, 255, ThresholdTypes.Binary);
myImshow("after threshold", threshold);
Cv2.WaitKey(0);
}
#endregion
#region 图像相减2:通过图像相减,查找扑克牌位置
public static void demo2()
{
Mat img1 = Cv2.ImRead("../../../images/subtract1.jpg", 0);
Mat img22 = Cv2.ImRead("../../../images/subtract2.jpg");
Mat img2 = new Mat();
Cv2.CvtColor(img22, img2, ColorConversionCodes.BGR2GRAY);
Mat st = new Mat();
Cv2.Subtract(img2, img1, st);
//Cv2.Subtract(img1, img2, st); // 相反
// 把小于5的像素点设为0
for (int i = 0; i < st.Rows; ++i)
{
for (int j = 0; j < st.Cols; ++j)
{
byte rgb = st.At<byte>(i, j);
if (rgb > 0 && rgb <= 5)
st.Set(i, j, 0);
}
}
Mat threshold = new Mat();
Cv2.Threshold(st, threshold, 50, 255, ThresholdTypes.Binary);
Point[][] contours = new Point[][] { };
HierarchyIndex[] hierarcy;
Cv2.FindContours(threshold, out contours, out hierarcy, RetrievalModes.Tree, ContourApproximationModes.ApproxSimple, null);
//List<(int, double)> areas = new List<(int, double)>();
//for (int i = 0; i < contours.Length; i++)
//{
// areas.Add((i, Cv2.ContourArea(contours[i])));
//}
//// 降序排序
//var a2 = areas.OrderByDescending(x => x.Item2).ToList();
//foreach(var are in a2)
//{
// if (are.Item2 < 100)
// continue;
// Cv2.DrawContours(img22, contours, are.Item1, new Scalar(0, 0, 255), 3);
// myImshow("approxPolyDP", img22);
// Cv2.WaitKey(0);
//}
// TODO 截取原图,把长方形纠正
Point[] cnt = contours[0];
Point[] hull = Cv2.ConvexHull(cnt);
double epsilon = 0.001 * Cv2.ArcLength(hull, true);
Point[] simplified_cnt = Cv2.ApproxPolyDP(hull, epsilon, true);
epsilon = 0.1 * Cv2.ArcLength(cnt, true);
Point[] approx = Cv2.ApproxPolyDP(cnt, epsilon, true);
Point[][] approxs = new Point[][] { approx };
Cv2.DrawContours(img22, approxs, 0, new Scalar(255, 0, 0), 3);
myImshow("approxPolyDP", img22);
Cv2.WaitKey(0);
}
#endregion
#region 图像相减3:通过图像相减,凸显扑克牌位置
// returns just the difference of the two images
public static Mat diff(Mat img, Mat img1)
{
Mat diff = new Mat();
Cv2.Absdiff(img, img1, diff);
return diff;
}
// removes the background but requires three images
public static Mat diff_remove_bg(Mat img0, Mat img, Mat img1)
{
Mat d1 = diff(img0, img);
Mat d2 = diff(img, img1);
Mat a = new Mat();
Cv2.BitwiseAnd(d1, d2, a);
return a;
}
public static void demo3()
{
Mat img1 = Cv2.ImRead("../../../images/subtract1.jpg", 0);
Mat img2 = Cv2.ImRead("../../../images/subtract2.jpg", 0);
myImshow("subtract1", img1);
myImshow("subtract2", img2);
Mat st = diff_remove_bg(img2, img1, img2);
myImshow("after subtract", st);
Cv2.WaitKey(0);
}
#endregion
#region 调用摄像头,通过图像相减,标记扑克牌位置
public static void demo4()
{
VideoCapture Cap = new VideoCapture();
Cap.Open(0);
// 判断摄像头是否成功打开
if (!Cap.IsOpened())
{
Console.WriteLine("摄像头打开失败.");
return;
}
/* opencv3 */
//Cap.Set(CaptureProperty.FrameWidth, 1280); // 设置采集的图像宽度
//Cap.Set(CaptureProperty.FrameHeight, 720); // 设置采集的图像高度
/* opencv4 */
Cap.Set(VideoCaptureProperties.FrameWidth, 1280); // 设置采集的图像宽度
Cap.Set(VideoCaptureProperties.FrameHeight, 720); // 设置采集的图像高度
//Cap.Set(VideoCaptureProperties.Exposure, 0); // 设置曝光值
Mat bgimg0 = new Mat();
Mat bgimg = new Mat();
int frame_no = 10; // 第10帧稳定图像
while (frame_no > 0)
{
if (Cap.Read(bgimg0))
frame_no--;
}
Cv2.CvtColor(bgimg0, bgimg, ColorConversionCodes.BGR2GRAY);
Mat frame = new Mat();
Mat gray = new Mat();
Mat st = new Mat();
Mat img = new Mat();
while (true)
{
if (Cap.Read(frame))
{
Cv2.CvtColor(frame, gray, ColorConversionCodes.BGR2GRAY);
Cv2.Subtract(gray, bgimg, st);
Mat threshold = new Mat();
Cv2.Threshold(st, threshold, 50, 255, ThresholdTypes.Binary);
Point[][] contours = new Point[][] { };
HierarchyIndex[] hierarcy;
Cv2.FindContours(threshold, out contours, out hierarcy, RetrievalModes.Tree, ContourApproximationModes.ApproxSimple, null);
Cv2.DrawContours(st, contours, -1, new Scalar(255, 255, 255), 3);
img = st;
for (int i = 0; i < contours.Length; i++)
{
var area = Cv2.ContourArea(contours[i]);
if (area < 200)
continue;
double peri = Cv2.ArcLength(contours[i], true);
Point[] approx = Cv2.ApproxPolyDP(contours[i], 0.04 * peri, true);
if (approx.Length == 4)
{
Rect rect = Cv2.BoundingRect(approx);
Cv2.Rectangle(frame, new Point(rect.X, rect.Y),
new Point(rect.X + rect.Width, rect.Y + rect.Height),
new Scalar(0, 0, 255), 2);
}
}
// TODO 对比前几/十几帧,新放一张扑克,知道是那张
// 等待图像稳定,不放牌后,再计算
myImshow("frame", frame);
myImshow("subtract", img);
myImshow("threshold", threshold);
Cv2.WaitKey(1);
}
}
}
#endregion
}
}
C++版本运行代码如下:
#include <opencv2/opencv.hpp>
#include <opencv2/cvconfig.h>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <io.h>
#include <string>
#include <iostream>
using namespace cv;
using namespace std;
void myImshow(string name, Mat img)
{
Mat temp;
cv::resize(img, temp, Size(img.cols * 1, img.rows * 1), 0, 0, 3);
cv::imshow(name, temp);
}
// 图像相减1:图像的减法运算
void demo1()
{
Mat img1 = cv::imread("../images/subtract1.jpg", 0); // 灰度图
Mat img2 = cv::imread("../images/subtract2.jpg", 0); // 灰度图
myImshow("subtract1", img1);
myImshow("subtract2", img2);
Mat st = img2 - img1;
myImshow("after subtract", st);
Mat threshold;
cv::threshold(st, threshold, 50, 255, THRESH_BINARY);
myImshow("after threshold", threshold);
cv::waitKey(0);
}
// 图像相减2:通过图像相减,查找扑克牌位置
void demo2()
{
Mat img1 = cv::imread("../images/subtract1.jpg", 0);
Mat img22 = cv::imread("../images/subtract2.jpg");
Mat img2;
cv::cvtColor(img22, img2, COLOR_BGR2GRAY);
Mat st;
cv::subtract(img2, img1, st);
//cv::Subtract(img1, img2, st); // 相反
// 把小于5的像素点设为0
for (int i = 0; i < st.rows; ++i)
{
for (int j = 0; j < st.cols; ++j)
{
char rgb = st.at<char>(i, j);
if (rgb > 0 && rgb <= 5)
st.at<char>(i, j) = 0;
}
}
Mat threshold;
cv::threshold(st, threshold, 50, 255, THRESH_BINARY);
vector<vector<Point>> contours;
vector<Vec4i> hierarcy;
findContours(threshold, contours, hierarcy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0));
//for (int i = 0; i < contours.size(); i++)
//{
// int area = cv::contourArea(contours[i]);
// if (area < 100)
// continue;
// cv::drawContours(img22, contours, i, Scalar(0, 0, 255), 3);
// myImshow("approxPolyDP", img22);
// cv::waitKey(0);
//}
// TODO 截取原图,把长方形纠正
vector<Point> cnt = contours[0];
vector<Point> hull;
cv::convexHull(cnt, hull);
double epsilon = 0.001 * cv::arcLength(hull, true);
std::vector<cv::Point> simplified_cnt;
cv::approxPolyDP(hull, simplified_cnt, epsilon, true);
epsilon = 0.1 * cv::arcLength(cnt, true);
vector<Point> approx;
cv::approxPolyDP(cnt, approx, epsilon, true);
vector<vector<Point>> approxs;
approxs.push_back(approx);
cv::drawContours(img22, approxs, 0, Scalar(255, 0, 0), 3);
myImshow("approxPolyDP", img22);
cv::waitKey(0);
}
// returns just the difference of the two images
Mat diff(Mat img, Mat img1)
{
Mat diff;
cv::absdiff(img, img1, diff);
return diff;
}
// removes the background but requires three images
Mat diff_remove_bg(Mat img0, Mat img, Mat img1)
{
Mat d1 = diff(img0, img);
Mat d2 = diff(img, img1);
Mat a;
cv::bitwise_and(d1, d2, a);
return a;
}
// 图像相减3:通过图像相减,凸显扑克牌位置
void demo3()
{
Mat img1 = cv::imread("../images/subtract1.jpg", 0);
Mat img2 = cv::imread("../images/subtract2.jpg", 0);
myImshow("subtract1", img1);
myImshow("subtract2", img2);
Mat st = diff_remove_bg(img2, img1, img2);
myImshow("after subtract", st);
cv::waitKey(0);
}
// 调用摄像头,通过图像相减,标记扑克牌位置
void demo4()
{
VideoCapture cap;
cap.open(0);
if (!cap.isOpened())
{
cout << "摄像头打开失败." << endl;
while (true);
return;
}
//cap.set(CAP_PROP_FRAME_WIDTH, 1280); // 宽度
//cap.set(CAP_PROP_FRAME_HEIGHT, 720); // 高度
//cap.set(CAP_PROP_EXPOSURE, 0); // 曝光
Mat bgimg0;
Mat bgimg;
int frame_no = 10; // 第10帧稳定图像
while (frame_no > 0)
{
cap >> bgimg0;
if (!bgimg0.empty())
{
frame_no--;
}
}
cv::cvtColor(bgimg0, bgimg, COLOR_BGR2GRAY);
Mat frame;
Mat gray;
Mat st;
Mat img;
while (true)
{
cap >> frame;
if (!frame.empty())
{
cv::cvtColor(frame, gray, COLOR_BGR2GRAY);
cv::subtract(gray, bgimg, st);
Mat threshold;
cv::threshold(st, threshold, 50, 255, THRESH_BINARY);
vector<vector<Point>> contours;
vector<Vec4i> hierarcy;
findContours(threshold, contours, hierarcy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0));
cv::drawContours(st, contours, -1, Scalar(255, 255, 255), 3);
img = st;
for (int i = 0; i < contours.size(); i++)
{
int area = cv::contourArea(contours[i]);
if (area < 200)
continue;
double peri = cv::arcLength(contours[i], true);
vector<Point> approx;
cv::approxPolyDP(contours[i], approx, 0.04 * peri, true);
if (approx.size() == 4)
{
Rect rect = cv::boundingRect(approx);
cv::rectangle(frame, Point(rect.x, rect.y),
Point(rect.x + rect.width, rect.y + rect.height),
Scalar(0, 0, 255), 2);
}
}
// TODO 对比前几/十几帧,新放一张扑克,知道是那张
// 等待图像稳定,不放牌后,再计算
myImshow("frame", frame);
myImshow("subtract", img);
myImshow("threshold", threshold);
cv::waitKey(1);
}
}
}
int main()
{
demo1();
//demo2();
//demo3();
//demo4();
}
Python版本运行效果及代码如下:
demo1.py:图像的减法运算:
import cv2
# img1=cv2.imread('../images/subtract1.jpg')
img1 = cv2.imread('../images/subtract1.jpg', 0) # 灰度图
# img2=cv2.imread('../images/subtract2.jpg')
img2 = cv2.imread('../images/subtract2.jpg', 0)
cv2.imshow('subtract1', img1)
cv2.imshow('subtract2', img2)
#
st = img2 - img1
# st=img1-img2#相反
cv2.imshow('after subtract', st)
# 效果好一点
# ret,threshold=cv2.threshold(st,0, 127, cv2.THRESH_BINARY)
ret, threshold = cv2.threshold(st, 50, 255, cv2.THRESH_BINARY)
cv2.imshow('after threshold', threshold)
cv2.waitKey(0)
demo2.py:通过图像相减,查找扑克牌位置:
import cv2
import numpy as np
import matplotlib.pyplot as plt
# img1=cv2.imread('../images/subtract1.jpg')
img1 = cv2.imread('../images/subtract1.jpg', 0) # 灰度图
# img2=cv2.imread('../images/subtract2.jpg')
# img2 = cv2.imread('../images/subtract2.jpg', 0)
img22 = cv2.imread('../images/subtract2.jpg')
img2 = cv2.cvtColor(img22, cv2.COLOR_BGR2GRAY)
# cv2.imshow('subtract1', img1)
# cv2.imshow('subtract2', img2)
#
st = cv2.subtract(img2, img1)
# st = cv2.subtract(img1, img2)#相反
st[st <= 5] = 0 # 把小于20的像素点设为0
# cv2.imshow('after subtract', st)
'''
# 直方图,看看大部分像素集中在哪个区域
# plt.plot(st)
pxs = st.ravel()
pxs=[x for x in pxs if x>5]#20,10
plt.hist(pxs, 256, [0, 256])
plt.show()
'''
# 效果好一点
# ret,threshold=cv2.threshold(st,0, 127, cv2.THRESH_BINARY)
ret, threshold = cv2.threshold(st, 50, 255, cv2.THRESH_BINARY)
# cv2.imshow('after threshold', threshold)
contours, hierarchy = cv2.findContours(threshold, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
areas = list()
for i, cnt in enumerate(contours):
areas.append((i, cv2.contourArea(cnt)))
#
a2 = sorted(areas, key=lambda d: d[1], reverse=True)
'''
for i,are in a2:
if are <100:
continue
cv2.drawContours(img22, contours, i, (0, 0, 255), 3)
print(i,are)
cv2.imshow('drawContours',img22)
cv2.waitKey(0)
# cv2.destroyAllWindows()
'''
# TODO 截取原图,把长方形纠正
cnt = contours[0]
print(cnt)
hull = cv2.convexHull(cnt)
epsilon = 0.001 * cv2.arcLength(hull, True)
simplified_cnt = cv2.approxPolyDP(hull, epsilon, True)
epsilon = 0.1 * cv2.arcLength(cnt, True)
approx = cv2.approxPolyDP(cnt, epsilon, True)
print(approx)
cv2.drawContours(img22, [approx], 0, (255, 0, 0), 3)
cv2.imshow('approxPolyDP', img22)
cv2.waitKey(0)
exit(3)
# findHomography(srcPoints, dstPoints, method=None, ransacReprojThreshold=None, mask=None, maxIters=None, confidence=None)
# H = cv2.findHomography(srcPoints=cnt.astype('single'), dstPoints=np.array([[[0., 0.]], [[2150., 0.]], [[2150., 2800.]], [[0., 2800.]]]))
# M, mask = cv2.findHomography(src_pts, dst_pts, cv2.RANSAC, 5.0)
# now that we have our screen contour, we need to determine
# the top-left, top-right, bottom-right, and bottom-left
# points so that we can later warp the image -- we'll start
# by reshaping our contour to be our finals and initializing
# our output rectangle in top-left, top-right, bottom-right,
# and bottom-left order
pts = approx.reshape(4, 2)
rect = np.zeros((4, 2), dtype="float32")
# the top-left point has the smallest sum whereas the
# bottom-right has the largest sum
s = pts.sum(axis=1)
rect[0] = pts[np.argmin(s)]
rect[2] = pts[np.argmax(s)]
# compute the difference between the points -- the top-right
# will have the minumum difference and the bottom-left will
# have the maximum difference
diff = np.diff(pts, axis=1)
rect[1] = pts[np.argmin(diff)]
rect[3] = pts[np.argmax(diff)]
# multiply the rectangle by the original ratio
ratio = image.shape[0] / 300.0
rect *= ratio
# now that we have our rectangle of points, let's compute
# the width of our new image
(tl, tr, br, bl) = rect
widthA = np.sqrt(((br[0] - bl[0]) ** 2) + ((br[1] - bl[1]) ** 2))
widthB = np.sqrt(((tr[0] - tl[0]) ** 2) + ((tr[1] - tl[1]) ** 2))
# ...and now for the height of our new image
heightA = np.sqrt(((tr[0] - br[0]) ** 2) + ((tr[1] - br[1]) ** 2))
heightB = np.sqrt(((tl[0] - bl[0]) ** 2) + ((tl[1] - bl[1]) ** 2))
# take the maximum of the width and height values to reach
# our final dimensions
maxWidth = max(int(widthA), int(widthB))
maxHeight = max(int(heightA), int(heightB))
# construct our destination points which will be used to
# map the screen to a top-down, "birds eye" view
dst = np.array([
[0, 0],
[maxWidth - 1, 0],
[maxWidth - 1, maxHeight - 1],
[0, maxHeight - 1]], dtype="float32")
# calculate the perspective transform matrix and warp
# the perspective to grab the screen
M = cv2.getPerspectiveTransform(rect, dst)
warp = cv2.warpPerspective(img22, M, (maxWidth, maxHeight))
# final_image = cv2.warpPerspective(img22, H, (2150, 2800))
cv2.imshow('final_image', warp)
cv2.waitKey(0)
demo3.py:通过图像相减,凸显扑克牌位置:
"""
图像相减3.py:
3张图片
"""
import cv2
def diff(img, img1): # returns just the difference of the two images
return cv2.absdiff(img, img1)
def diff_remove_bg(img0, img, img1): # removes the background but requires three images
d1 = diff(img0, img)
d2 = diff(img, img1)
return cv2.bitwise_and(d1, d2)
# img1=cv2.imread('subtract1.jpg')
img1 = cv2.imread('subtract1.jpg', 0) # 灰度图
# img2=cv2.imread('subtract2.jpg')
img2 = cv2.imread('subtract2.jpg', 0)
cv2.imshow('subtract1', img1)
cv2.imshow('subtract2', img2)
st = diff_remove_bg(img2, img1,img2)
cv2.imshow('after subtract', st)
cv2.waitKey(0)
demo4.py:调用摄像头,通过图像相减,标记扑克牌位置:
import cv2
import numpy as np
def myImshow(name,img):
imgH = img.shape[0]
imgW = img.shape[1]
temp = cv2.resize(img, ((int)(imgW*0.5), (int)(imgH*0.5)), interpolation = cv2.INTER_NEAREST)
cv2.imshow(name, temp)
# 外部摄像头默认好像是CAP_MSMF格式,会导致摄像头无法打开,设置成CAP_DSHOW就可以了
cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)
#ret = cap.set(3, 640)
#ret = cap.set(4, 480)
ret = cap.set(cv2.CAP_PROP_FRAME_WIDTH,1280) # 设置图像宽
ret = cap.set(cv2.CAP_PROP_FRAME_HEIGHT,720) # 设置图像高
ret = cap.set(cv2.CAP_PROP_EXPOSURE, 0) # 设置曝光值
cap.read()
cap.read()
cap.read()
cap.read()
cap.read()
cap.read()
cap.read()
cap.read()
cap.read()
cap.read()
cap.read()
cap.read()
cap.read()
cap.read()
'''
cal=[cap.read()[1] for x in range(20)]
#mean 直接的加减是不行的
# bgimg0=np.mean(np.sum(cal))
# bgimg0=np.average(cal)
# bgimg0=np.mean(cal)
nps1=sum(cal)
mean1=nps1/len(cal)
# mean1[mean1<0]=0
# mean1[mean1>255]=255
cv2.imshow('bgimg', mean1)
cv2.waitKey(0)
exit(3)
'''
frame_no = 100
# cap.set(1, frame_no)#第10帧
ret, bgimg0 = cap.read() # 背景
bgimg = cv2.cvtColor(bgimg0, cv2.COLOR_BGR2GRAY)
myImshow('bgimg' + str(frame_no), bgimg0)
# cv2.imwrite('desk_bgimg.jpg',bgimg)
while cap.isOpened():
ret, frame = cap.read() # TODO 图像稳定
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
#
st = cv2.subtract(gray, bgimg)
# st = cv2.subtract(img1, img2)#相反
# st[st <= 5] = 0 # 把小于20的像素点设为0
ret, threshold = cv2.threshold(st, 50, 255, cv2.THRESH_BINARY)
contours, hierarchy = cv2.findContours(threshold, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
print("contours size: ", len(contours))
# img = cv2.drawContours(st, contours, -1, (0, 0, 0), 13)
img = cv2.drawContours(st, contours, -1, (255, 255, 255), 3)
#
for cnt in contours:
area = cv2.contourArea(cnt)
if area < 200:
continue
peri = cv2.arcLength(cnt, True)
approx = cv2.approxPolyDP(cnt, 0.04 * peri, True)
if len(approx) == 4:
(x, y, w, h) = cv2.boundingRect(approx)
cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 0, 255), 2)
# TODO 对比前几/十几帧,新放一张扑克,知道是那张
# 等待图像稳定,不放牌后,再计算
myImshow("frame", frame)
myImshow("subtract", img)
# cv2.moveWindow("subtract", y=bgimg.shape[0], x=0)
myImshow('threshold', threshold)
# cv2.moveWindow("threshold", x=bgimg.shape[1], y=0)
key = cv2.waitKey(delay=1)
if key == ord("q"):
break
elif key == ord("s"):
cv2.imwrite('poker-threshold.jpg', threshold)
cv2.destroyAllWindows()